├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── build.yaml ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── build.excerpt.yaml ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── main.dart │ ├── pages │ │ ├── post_detail │ │ │ ├── post_detail_page.dart │ │ │ ├── post_detail_page_scenes.dart │ │ │ └── post_detail_page_scenes.stager_app.g.dart │ │ ├── posts_list │ │ │ ├── posts_list_page.dart │ │ │ ├── posts_list_page_scenes.dart │ │ │ ├── posts_list_page_scenes.mocks.dart │ │ │ └── posts_list_page_scenes.stager_app.g.dart │ │ └── user_detail │ │ │ ├── user_detail_page.dart │ │ │ ├── user_detail_page_scenes.dart │ │ │ └── user_detail_page_scenes.stager_app.g.dart │ └── shared │ │ ├── api.dart │ │ ├── post.dart │ │ ├── post_card │ │ ├── post_card.dart │ │ ├── post_card_scenes.dart │ │ └── post_card_scenes.stager_app.g.dart │ │ ├── posts_list │ │ └── posts_list.dart │ │ └── user.dart ├── linux │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ │ ├── CMakeLists.txt │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ └── generated_plugins.cmake │ ├── main.cc │ ├── my_application.cc │ └── my_application.h ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ └── MainMenu.xib │ │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements ├── pubspec.lock ├── pubspec.yaml ├── test │ ├── pages │ │ ├── post_detail_page_test.dart │ │ ├── posts_list_page_test.dart │ │ └── user_detail_page_test.dart │ └── scene_test_extensions.dart ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json └── windows │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake │ └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── lib ├── builder.dart ├── src │ ├── environment │ │ ├── controls │ │ │ ├── boolean_control.dart │ │ │ ├── dropdown_control.dart │ │ │ ├── environment_control.dart │ │ │ ├── stepper_control.dart │ │ │ └── text_input_control.dart │ │ ├── environment_control_panel.dart │ │ └── state │ │ │ ├── environment_state.dart │ │ │ └── screen_size_preset.dart │ ├── environment_aware_app.dart │ ├── extensions │ │ └── string_extensions.dart │ ├── scene.dart │ ├── scene_container.dart │ ├── scene_list.dart │ ├── stager_app.dart │ └── stager_app_generator.dart └── stager.dart ├── pubspec.lock ├── pubspec.yaml └── test ├── environment_control_panel_test.dart ├── scene_container_test.dart └── stager_app_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build output. 6 | build/ 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Better support for the environment control panel on larger screens. 4 | - Overhaul of environment control system, including custom controls. 5 | - Updated license from MIT to Apache 2.0, changed copyright to Google. 6 | 7 | ## 0.2.2 8 | 9 | This version includes a complete overhaul of the environment control panel. 10 | 11 | - Adds new functionality to environment control panel (device sizes, bold text). 12 | - Scenes can now add their own widgets to the environment control panel. 13 | 14 | ## 0.1.0 15 | 16 | - Initial version. 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Unfortunately, we are not accepting contributions at this time. 2 | -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | targets: 16 | $default: 17 | builders: 18 | stager|stagerAppBuilder: 19 | generate_for: 20 | - example/**.dart 21 | 22 | builders: 23 | stagerAppBuilder: 24 | import: "package:stager/builder.dart" 25 | builder_factories: ["buildStagerApp"] 26 | build_extensions: {".dart": [".stager_app.dart"]} 27 | build_to: source 28 | auto_apply: dependents -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 8 | channel: master 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 17 | base_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 18 | - platform: android 19 | create_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 20 | base_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 21 | - platform: ios 22 | create_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 23 | base_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 24 | - platform: linux 25 | create_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 26 | base_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 27 | - platform: macos 28 | create_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 29 | base_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 30 | - platform: web 31 | create_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 32 | base_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 33 | - platform: windows 34 | create_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 35 | base_revision: 80a36fcb0c4845085c150a83d1b63adc8c9a88ec 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Stager Example 2 | 3 | A barebones sample app demonstrating how and why you might want to use Scenes. 4 | 5 | ## Overview 6 | 7 | This app shows a Twitter-like UI. It launches to a list of posts, from which you can 8 | navigate to a post detail page. From the post detail page, you can navigate to a user detail page, 9 | which shows the user's name and their posts. 10 | 11 | To demonstrate how this works with DI and testing, this also uses [package:provider](https://pub.dev/packages/provider) 12 | and [package:mockito](https://pub.dev/packages/mockito). These only serve to illustrate one way you might 13 | use Stager and are not a requirement. 14 | 15 | The app has three pages, each of which have their own scenes. These scenes exercise several different states 16 | for each page, some of which might be a bit diffcult or cumbersome to develop otherwise. For example, developing 17 | a loading screen without Stager might require: 18 | 19 | 1. A one-off edit to the page code to permanently show a loading state. This would almost certainly never be 20 | committed to source control, and any future developers who want to test this would need to duplicate the work. 21 | 2. Using Network Link Conditioner or a similar tool to simulate a bad network connection. This is better than 22 | option 1, but will not be useful when attempting to simulate other states (e.g., empty). 23 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | def localProperties = new Properties() 18 | def localPropertiesFile = rootProject.file('local.properties') 19 | if (localPropertiesFile.exists()) { 20 | localPropertiesFile.withReader('UTF-8') { reader -> 21 | localProperties.load(reader) 22 | } 23 | } 24 | 25 | def flutterRoot = localProperties.getProperty('flutter.sdk') 26 | if (flutterRoot == null) { 27 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 28 | } 29 | 30 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 31 | if (flutterVersionCode == null) { 32 | flutterVersionCode = '1' 33 | } 34 | 35 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 36 | if (flutterVersionName == null) { 37 | flutterVersionName = '1.0' 38 | } 39 | 40 | apply plugin: 'com.android.application' 41 | apply plugin: 'kotlin-android' 42 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 43 | 44 | android { 45 | compileSdkVersion flutter.compileSdkVersion 46 | ndkVersion flutter.ndkVersion 47 | 48 | compileOptions { 49 | sourceCompatibility JavaVersion.VERSION_1_8 50 | targetCompatibility JavaVersion.VERSION_1_8 51 | } 52 | 53 | kotlinOptions { 54 | jvmTarget = '1.8' 55 | } 56 | 57 | sourceSets { 58 | main.java.srcDirs += 'src/main/kotlin' 59 | } 60 | 61 | defaultConfig { 62 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 63 | applicationId "com.example.example" 64 | // You can update the following values to match your application needs. 65 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 66 | minSdkVersion flutter.minSdkVersion 67 | targetSdkVersion flutter.targetSdkVersion 68 | versionCode flutterVersionCode.toInteger() 69 | versionName flutterVersionName 70 | } 71 | 72 | buildTypes { 73 | release { 74 | // TODO: Add your own signing config for the release build. 75 | // Signing with the debug keys for now, so `flutter run --release` works. 76 | signingConfig signingConfigs.debug 77 | } 78 | } 79 | } 80 | 81 | flutter { 82 | source '../..' 83 | } 84 | 85 | dependencies { 86 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 87 | } 88 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 23 | 31 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.example.example 18 | 19 | import io.flutter.embedding.android.FlutterActivity 20 | 21 | class MainActivity: FlutterActivity() { 22 | } 23 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 25 | 31 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 25 | 31 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | buildscript { 18 | ext.kotlin_version = '1.6.10' 19 | repositories { 20 | google() 21 | mavenCentral() 22 | } 23 | 24 | dependencies { 25 | classpath 'com.android.tools.build:gradle:7.1.2' 26 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 27 | } 28 | } 29 | 30 | allprojects { 31 | repositories { 32 | google() 33 | mavenCentral() 34 | } 35 | } 36 | 37 | rootProject.buildDir = '../build' 38 | subprojects { 39 | project.buildDir = "${rootProject.buildDir}/${project.name}" 40 | } 41 | subprojects { 42 | project.evaluationDependsOn(':app') 43 | } 44 | 45 | task clean(type: Delete) { 46 | delete rootProject.buildDir 47 | } 48 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 6 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | include ':app' 18 | 19 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 20 | def properties = new Properties() 21 | 22 | assert localPropertiesFile.exists() 23 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 24 | 25 | def flutterSdkPath = properties.getProperty("flutter.sdk") 26 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 27 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 28 | -------------------------------------------------------------------------------- /example/build.excerpt.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | targets: 16 | $default: 17 | sources: 18 | include: 19 | - lib/** 20 | - test/** 21 | # Some default includes that aren't really used here but will prevent 22 | # false-negative warnings: 23 | - $package$ 24 | - lib/$lib$ 25 | exclude: 26 | - '**/.*/**' 27 | - '**/build/**' 28 | builders: 29 | code_excerpter|code_excerpter: 30 | enabled: true 31 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import UIKit 18 | import Flutter 19 | 20 | @UIApplicationMain 21 | @objc class AppDelegate: FlutterAppDelegate { 22 | override func application( 23 | _ application: UIApplication, 24 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 25 | ) -> Bool { 26 | GeneratedPluginRegistrant.register(with: self) 27 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #import "GeneratedPluginRegistrant.h" 18 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | import 'package:provider/provider.dart'; 19 | 20 | import 'pages/posts_list/posts_list_page.dart'; 21 | import 'shared/api.dart'; 22 | 23 | void main() { 24 | runApp(const MyApp()); 25 | } 26 | 27 | /// The main app. 28 | class MyApp extends StatelessWidget { 29 | /// Creates a [MyApp]. 30 | const MyApp({super.key}); 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Provider.value( 35 | value: Api(), 36 | child: MaterialApp( 37 | title: 'Flutter Demo', 38 | theme: ThemeData( 39 | primarySwatch: Colors.blue, 40 | ), 41 | home: const PostsListPage(), 42 | ), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /example/lib/pages/post_detail/post_detail_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../../shared/post.dart'; 20 | import '../user_detail/user_detail_page.dart'; 21 | 22 | /// A page for a single [Post]. 23 | class PostDetailPage extends StatelessWidget { 24 | /// Creates a [PostDetailPage]. 25 | const PostDetailPage({super.key, required this.post}); 26 | 27 | /// The [Post] being displayed. 28 | final Post post; 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return Scaffold( 33 | appBar: AppBar( 34 | title: const Text('Post'), 35 | actions: >[ 36 | PopupMenuButton( 37 | onSelected: (_) { 38 | final NavigatorState navigatorstate = Navigator.of(context); 39 | navigatorstate.push( 40 | MaterialPageRoute( 41 | builder: (BuildContext context) => 42 | UserDetailPage(user: post.author), 43 | ), 44 | ); 45 | }, 46 | itemBuilder: (BuildContext context) => >[ 47 | const PopupMenuItem( 48 | value: 0, 49 | child: Text('View User'), 50 | ), 51 | ], 52 | ) 53 | ], 54 | ), 55 | body: Padding( 56 | padding: const EdgeInsets.all(10), 57 | child: Column( 58 | crossAxisAlignment: CrossAxisAlignment.start, 59 | children: [ 60 | Row( 61 | children: [ 62 | CircleAvatar( 63 | child: Text( 64 | post.author.name 65 | .split(' ') 66 | .map((String e) => e[0].toUpperCase()) 67 | .join(), 68 | ), 69 | ), 70 | const SizedBox(width: 10), 71 | Text(post.time.toString()), 72 | ], 73 | ), 74 | const SizedBox(height: 10), 75 | Text( 76 | post.text, 77 | style: Theme.of(context).textTheme.headlineSmall, 78 | ), 79 | ], 80 | ), 81 | ), 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /example/lib/pages/post_detail/post_detail_page_scenes.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | import 'package:stager/stager.dart'; 19 | 20 | import '../../shared/post.dart'; 21 | import 'post_detail_page.dart'; 22 | 23 | /// A scene demonstrating a [PostDetailPage] with content. 24 | class PostDetailPageScene extends Scene { 25 | /// The [EnvironmentState] key that maps to the currentPost value. 26 | static const String _currentPostKey = 'PostDetailPageScene.CurrentPost'; 27 | 28 | /// Creates an [EnvironmentControl] that allows the user to choose which 29 | /// [Post] is displayed in this Scene. 30 | final DropdownControl postSelectorControl = DropdownControl( 31 | title: 'Post', 32 | stateKey: _currentPostKey, 33 | defaultValue: Post.fakePosts().first, 34 | items: Post.fakePosts(), 35 | ); 36 | 37 | @override 38 | String get title => 'Post Detail'; 39 | 40 | /// This [Scene] overrides the otional [environmentControls] getter to add a 41 | /// custom control to the Stager environment control panel. 42 | @override 43 | late final List> environmentControls = 44 | >[ 45 | postSelectorControl, 46 | ]; 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return EnvironmentAwareApp( 51 | home: PostDetailPage( 52 | post: postSelectorControl.currentValue, 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /example/lib/pages/post_detail/post_detail_page_scenes.stager_app.g.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // GENERATED CODE - DO NOT MODIFY BY HAND 18 | 19 | // ************************************************************************** 20 | // StagerAppGenerator 21 | // ************************************************************************** 22 | 23 | import 'package:stager/stager.dart'; 24 | 25 | import 'post_detail_page_scenes.dart'; 26 | 27 | void main() { 28 | final List scenes = [ 29 | PostDetailPageScene(), 30 | ]; 31 | 32 | if (const String.fromEnvironment('Scene').isNotEmpty) { 33 | const String sceneName = String.fromEnvironment('Scene'); 34 | final Scene scene = 35 | scenes.firstWhere((Scene scene) => scene.title == sceneName); 36 | runStagerApp(scenes: [scene]); 37 | } else { 38 | runStagerApp(scenes: scenes); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/lib/pages/posts_list/posts_list_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | import 'package:provider/provider.dart'; 19 | 20 | import '../../shared/api.dart'; 21 | import '../../shared/post.dart'; 22 | import '../../shared/posts_list/posts_list.dart'; 23 | 24 | /// Shows a timeline view of all [Post]s. 25 | class PostsListPage extends StatefulWidget { 26 | /// Creates a [PostsListPage]. 27 | const PostsListPage({super.key}); 28 | 29 | @override 30 | State createState() => _PostsListPageState(); 31 | } 32 | 33 | class _PostsListPageState extends State { 34 | late Future> _fetchPostsFuture; 35 | 36 | @override 37 | void initState() { 38 | super.initState(); 39 | _fetchPostsFuture = Provider.of(context, listen: false).fetchPosts(); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return Scaffold( 45 | appBar: AppBar( 46 | title: const Text('Posts'), 47 | ), 48 | body: PostsList.fromFuture(_fetchPostsFuture), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /example/lib/pages/posts_list/posts_list_page_scenes.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'dart:async'; 18 | import 'dart:math'; 19 | 20 | import 'package:flutter/material.dart'; 21 | import 'package:mockito/annotations.dart'; 22 | import 'package:mockito/mockito.dart'; 23 | import 'package:provider/provider.dart'; 24 | import 'package:stager/stager.dart'; 25 | 26 | import '../../shared/api.dart'; 27 | import '../../shared/post.dart'; 28 | import 'posts_list_page.dart'; 29 | 30 | // #docregion PostsListPageScene 31 | @GenerateMocks([Api]) 32 | import 'posts_list_page_scenes.mocks.dart'; 33 | 34 | /// Defines a shared build method used by subclasses and a [MockApi] subclasses 35 | /// can use to control the behavior of the [PostsListPage]. 36 | abstract class BasePostsListScene extends Scene { 37 | /// A mock dependency of [PostsListPage]. Mock the value of [Api.fetchPosts] 38 | /// to put the staged [PostsListPage] into different states. 39 | late MockApi mockApi; 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return EnvironmentAwareApp( 44 | home: Provider.value( 45 | value: mockApi, 46 | child: const PostsListPage(), 47 | ), 48 | ); 49 | } 50 | 51 | @override 52 | Future setUp() async { 53 | mockApi = MockApi(); 54 | } 55 | } 56 | 57 | /// A Scene showing the [PostsListPage] with no [Post]s. 58 | class EmptyListScene extends BasePostsListScene { 59 | @override 60 | String get title => 'Empty List'; 61 | 62 | @override 63 | Future setUp() async { 64 | await super.setUp(); 65 | when(mockApi.fetchPosts()).thenAnswer((_) async => []); 66 | } 67 | } 68 | // #enddocregion PostsListPageScene 69 | 70 | /// A Scene showing the [PostsListPage] with [Post]s. 71 | class WithPostsScene extends BasePostsListScene { 72 | /// Allows the number of posts shown in this Scene to be set in the control 73 | /// panel. 74 | /// 75 | /// Because [PostListsPage] issues a request to fetch posts in [initState], 76 | /// we need to call [setNeedsReconstruct] in the onValueUpdated callback to 77 | /// tell Stager to fully recreate this Scene. 78 | late final StepperControl postCountStepperControl = StepperControl( 79 | title: 'Post Count', 80 | stateKey: 'WithPostsScene.PostCount', 81 | defaultValue: Post.fakePosts().length, 82 | onDecrementPressed: (int currentValue) => max(0, currentValue - 1), 83 | onIncrementPressed: (int currentValue) => 84 | min(currentValue + 1, Post.fakePosts().length), 85 | onValueUpdated: (_) => setNeedsReconstruct(), 86 | ); 87 | 88 | @override 89 | String get title => 'With Posts'; 90 | 91 | @override 92 | late final List> environmentControls = 93 | >[ 94 | postCountStepperControl, 95 | ]; 96 | 97 | @override 98 | Future setUp() async { 99 | await super.setUp(); 100 | when(mockApi.fetchPosts()).thenAnswer((_) async { 101 | return Post.fakePosts() 102 | .take(postCountStepperControl.currentValue) 103 | .toList(); 104 | }); 105 | } 106 | } 107 | 108 | /// A Scene showing the [PostsListPage] in a loading state. 109 | class LoadingScene extends BasePostsListScene { 110 | @override 111 | String get title => 'Loading'; 112 | 113 | @override 114 | Future setUp() async { 115 | await super.setUp(); 116 | final Completer> completer = Completer>(); 117 | when(mockApi.fetchPosts()).thenAnswer((_) async => completer.future); 118 | } 119 | } 120 | 121 | /// A Scene showing the [PostsListPage] in a error state. 122 | class ErrorScene extends BasePostsListScene { 123 | @override 124 | String get title => 'Error'; 125 | 126 | @override 127 | Future setUp() async { 128 | await super.setUp(); 129 | when(mockApi.fetchPosts()).thenAnswer( 130 | (_) => Future>.error(Exception()), 131 | ); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /example/lib/pages/posts_list/posts_list_page_scenes.mocks.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Mocks generated by Mockito 5.3.2 from annotations 18 | // in stager_example/pages/posts_list/posts_list_page_scenes.dart. 19 | // Do not manually edit this file. 20 | 21 | // ignore_for_file: no_leading_underscores_for_library_prefixes 22 | import 'dart:async' as _i3; 23 | 24 | import 'package:mockito/mockito.dart' as _i1; 25 | import 'package:stager_example/shared/api.dart' as _i2; 26 | import 'package:stager_example/shared/post.dart' as _i4; 27 | import 'package:stager_example/shared/user.dart' as _i5; 28 | 29 | // ignore_for_file: type=lint 30 | // ignore_for_file: avoid_redundant_argument_values 31 | // ignore_for_file: avoid_setters_without_getters 32 | // ignore_for_file: comment_references 33 | // ignore_for_file: implementation_imports 34 | // ignore_for_file: invalid_use_of_visible_for_testing_member 35 | // ignore_for_file: prefer_const_constructors 36 | // ignore_for_file: unnecessary_parenthesis 37 | // ignore_for_file: camel_case_types 38 | // ignore_for_file: subtype_of_sealed_class 39 | 40 | /// A class which mocks [Api]. 41 | /// 42 | /// See the documentation for Mockito's code generation for more information. 43 | class MockApi extends _i1.Mock implements _i2.Api { 44 | MockApi() { 45 | _i1.throwOnMissingStub(this); 46 | } 47 | 48 | @override 49 | _i3.Future> fetchPosts({_i5.User? user}) => 50 | (super.noSuchMethod( 51 | Invocation.method( 52 | #fetchPosts, 53 | [], 54 | {#user: user}, 55 | ), 56 | returnValue: _i3.Future>.value(<_i4.Post>[]), 57 | ) as _i3.Future>); 58 | } 59 | -------------------------------------------------------------------------------- /example/lib/pages/posts_list/posts_list_page_scenes.stager_app.g.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // GENERATED CODE - DO NOT MODIFY BY HAND 18 | 19 | // ************************************************************************** 20 | // StagerAppGenerator 21 | // ************************************************************************** 22 | 23 | import 'package:stager/stager.dart'; 24 | 25 | import 'posts_list_page_scenes.dart'; 26 | 27 | void main() { 28 | final List scenes = [ 29 | EmptyListScene(), 30 | WithPostsScene(), 31 | LoadingScene(), 32 | ErrorScene(), 33 | ]; 34 | 35 | if (const String.fromEnvironment('Scene').isNotEmpty) { 36 | const String sceneName = String.fromEnvironment('Scene'); 37 | final Scene scene = 38 | scenes.firstWhere((Scene scene) => scene.title == sceneName); 39 | runStagerApp(scenes: [scene]); 40 | } else { 41 | runStagerApp(scenes: scenes); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example/lib/pages/user_detail/user_detail_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | import 'package:provider/provider.dart'; 19 | 20 | import '../../shared/api.dart'; 21 | import '../../shared/post.dart'; 22 | import '../../shared/posts_list/posts_list.dart'; 23 | import '../../shared/user.dart'; 24 | 25 | /// A page for a single [User]. 26 | class UserDetailPage extends StatefulWidget { 27 | /// Creates a [UserDetailPage] which displays information about [user]. 28 | const UserDetailPage({super.key, required this.user}); 29 | 30 | /// The [User] whose info is being displayed. 31 | final User user; 32 | 33 | @override 34 | State createState() => _UserDetailPageState(); 35 | } 36 | 37 | class _UserDetailPageState extends State { 38 | late Future> _userPostsFuture; 39 | 40 | @override 41 | void initState() { 42 | super.initState(); 43 | 44 | _userPostsFuture = Provider.of(context, listen: false).fetchPosts( 45 | user: widget.user, 46 | ); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return Scaffold( 52 | appBar: AppBar( 53 | title: Text( 54 | widget.user.name, 55 | ), 56 | ), 57 | body: Column( 58 | children: [ 59 | Padding( 60 | padding: const EdgeInsets.all(10), 61 | child: Text( 62 | '${widget.user.name} (${widget.user.handle})', 63 | style: Theme.of(context).textTheme.displaySmall, 64 | ), 65 | ), 66 | Expanded( 67 | child: PostsList.fromFuture(_userPostsFuture), 68 | ), 69 | ], 70 | ), 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /example/lib/pages/user_detail/user_detail_page_scenes.stager_app.g.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // GENERATED CODE - DO NOT MODIFY BY HAND 18 | 19 | // ************************************************************************** 20 | // StagerAppGenerator 21 | // ************************************************************************** 22 | 23 | import 'package:stager/stager.dart'; 24 | 25 | import 'user_detail_page_scenes.dart'; 26 | 27 | void main() { 28 | final List scenes = [ 29 | LoadingUserDetailPageScene(), 30 | ErrorUserDetailPageScene(), 31 | EmptyUserDetailPageScene(), 32 | WithPostsUserDetailPageScene(), 33 | ComplexUserDetailPageScene(), 34 | ]; 35 | 36 | if (const String.fromEnvironment('Scene').isNotEmpty) { 37 | const String sceneName = String.fromEnvironment('Scene'); 38 | final Scene scene = 39 | scenes.firstWhere((Scene scene) => scene.title == sceneName); 40 | runStagerApp(scenes: [scene]); 41 | } else { 42 | runStagerApp(scenes: scenes); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/lib/shared/api.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'post.dart'; 18 | import 'user.dart'; 19 | 20 | /// A fake class meant to represent an API client. 21 | class Api { 22 | /// Waits 2 seconds and returns [Post.fakePosts]. 23 | Future> fetchPosts({User? user}) async { 24 | await Future.delayed(const Duration(seconds: 2)); 25 | return Post.fakePosts(user: user); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/lib/shared/post.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:equatable/equatable.dart'; 18 | 19 | import 'user.dart'; 20 | 21 | /// A single tweet-like entry. 22 | class Post extends Equatable { 23 | /// Creates a [Post]. 24 | const Post({ 25 | required this.id, 26 | required this.text, 27 | required this.author, 28 | required this.time, 29 | }); 30 | 31 | /// The id of this post. 32 | final int id; 33 | 34 | /// The content of this post. 35 | final String text; 36 | 37 | /// The author of this post. 38 | final User author; 39 | 40 | /// When this post was created. 41 | final DateTime time; 42 | 43 | @override 44 | List get props => [id, text, author, time]; 45 | 46 | @override 47 | String toString() { 48 | return '$author: $text'; 49 | } 50 | 51 | /// Generates a List of [Post]s. If [user] is specified, all posts will have 52 | /// that user as an author. If no [user] is specified, each [Post] will have a 53 | /// distinct fake [User] as its author. 54 | static List fakePosts({User? user}) => List.generate( 55 | 20, 56 | (int index) => Post( 57 | id: index + 1, 58 | text: 'Post ${index + 1}', 59 | author: user ?? User.fakeUser(id: index + 1), 60 | time: DateTime(2023, 1, 1, 1).add(Duration(minutes: index)), 61 | ), 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /example/lib/shared/post_card/post_card.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../post.dart'; 20 | 21 | /// A [Card] that displays a single [Post], intended to be used in a list. 22 | class PostCard extends StatelessWidget { 23 | /// Creates a tappable [PostCard] displaying [post]. 24 | const PostCard({super.key, required this.post, this.onTap}); 25 | 26 | /// The post being displayed. 27 | final Post post; 28 | 29 | /// Executed when this [PostCard] is tapped. 30 | final VoidCallback? onTap; 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Card( 35 | child: InkWell( 36 | onTap: onTap, 37 | child: Padding( 38 | padding: const EdgeInsets.all(10), 39 | child: Column( 40 | crossAxisAlignment: CrossAxisAlignment.start, 41 | children: [ 42 | Row( 43 | children: [ 44 | Text(post.author.handle), 45 | const Spacer(), 46 | Text('${post.time}'), 47 | ], 48 | ), 49 | const SizedBox(height: 20), 50 | Text( 51 | post.text, 52 | style: Theme.of(context).textTheme.titleLarge, 53 | ), 54 | ], 55 | ), 56 | ), 57 | ), 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /example/lib/shared/post_card/post_card_scenes.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | import 'package:stager/stager.dart'; 19 | 20 | import '../post.dart'; 21 | import '../user.dart'; 22 | import 'post_card.dart'; 23 | 24 | /// A Scene showing a single [PostCard] widget with a fake [Post]. 25 | class PostCardScene extends Scene { 26 | @override 27 | String get title => 'Single Card'; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return PostCard( 32 | post: Post.fakePosts().first, 33 | onTap: () {}, 34 | ); 35 | } 36 | } 37 | 38 | /// A Scene showing a [PostsList] with fake [Post]s. 39 | class PostsListScene extends Scene { 40 | /// The posts being shown in this scene. 41 | final List posts = [ 42 | Post( 43 | id: 1, 44 | author: User.joeUser, 45 | text: 'Hello, this is a post', 46 | time: DateTime(2022, 7, 28, 10, 30), 47 | ), 48 | Post( 49 | id: 2, 50 | author: User.aliceUser, 51 | text: 'Hi, Joe! Nice to hear from you. This is a much longer reply ' 52 | 'intended to test text wrapping in the PostCard widget.', 53 | time: DateTime(2022, 7, 28, 11, 30), 54 | ), 55 | Post( 56 | id: 3, 57 | author: User.joeUser, 58 | text: 'Hi Alice! Thanks for testing that.', 59 | time: DateTime(2022, 7, 28, 11, 45), 60 | ), 61 | ]; 62 | 63 | @override 64 | String get title => 'Card List'; 65 | 66 | @override 67 | Widget build(BuildContext context) { 68 | return EnvironmentAwareApp( 69 | home: Container( 70 | color: Theme.of(context).colorScheme.background, 71 | padding: const EdgeInsets.symmetric(horizontal: 10), 72 | child: ListView.builder( 73 | itemCount: posts.length, 74 | itemBuilder: (BuildContext context, int index) => PostCard( 75 | post: posts[index], 76 | onTap: () {}, 77 | ), 78 | ), 79 | ), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /example/lib/shared/post_card/post_card_scenes.stager_app.g.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // GENERATED CODE - DO NOT MODIFY BY HAND 18 | 19 | // ************************************************************************** 20 | // StagerAppGenerator 21 | // ************************************************************************** 22 | 23 | import 'package:stager/stager.dart'; 24 | 25 | import 'post_card_scenes.dart'; 26 | 27 | void main() { 28 | final List scenes = [ 29 | PostCardScene(), 30 | PostsListScene(), 31 | ]; 32 | 33 | if (const String.fromEnvironment('Scene').isNotEmpty) { 34 | const String sceneName = String.fromEnvironment('Scene'); 35 | final Scene scene = 36 | scenes.firstWhere((Scene scene) => scene.title == sceneName); 37 | runStagerApp(scenes: [scene]); 38 | } else { 39 | runStagerApp(scenes: scenes); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/lib/shared/posts_list/posts_list.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../../pages/post_detail/post_detail_page.dart'; 20 | import '../post.dart'; 21 | import '../post_card/post_card.dart'; 22 | 23 | // #docregion PostsList 24 | /// A [ListView] of [PostCard]s 25 | class PostsList extends StatefulWidget { 26 | /// Creates a [PostsList] displaying [posts]. 27 | /// 28 | /// [postsFuture] will be set to the value of [posts]. 29 | PostsList({ 30 | Key? key, 31 | required List posts, 32 | }) : this.fromFuture(key: key, Future>.value(posts)); 33 | 34 | /// Creates a [PostsList] with a Future that resolves to a list of [Post]s. 35 | const PostsList.fromFuture(this.postsFuture, {super.key}); 36 | 37 | /// The Future that resolves to the list of [Post]s this widget will display. 38 | final Future> postsFuture; 39 | 40 | @override 41 | State createState() => _PostsListState(); 42 | } 43 | 44 | class _PostsListState extends State { 45 | @override 46 | Widget build(BuildContext context) { 47 | return FutureBuilder>( 48 | future: widget.postsFuture, 49 | builder: (BuildContext context, AsyncSnapshot> snapshot) { 50 | if (snapshot.connectionState == ConnectionState.waiting) { 51 | return const Center( 52 | child: CircularProgressIndicator(), 53 | ); 54 | } 55 | 56 | if (snapshot.hasError) { 57 | return const Center( 58 | child: Text('Error'), 59 | ); 60 | } 61 | 62 | final List? posts = snapshot.data; 63 | if (posts == null || posts.isEmpty) { 64 | return const Center( 65 | child: Text('No posts'), 66 | ); 67 | } 68 | 69 | return ListView.builder( 70 | itemBuilder: (BuildContext context, int index) => PostCard( 71 | post: posts[index], 72 | onTap: () { 73 | Navigator.of(context).push( 74 | MaterialPageRoute( 75 | builder: (BuildContext context) => PostDetailPage( 76 | post: posts[index], 77 | ), 78 | ), 79 | ); 80 | }, 81 | ), 82 | itemCount: posts.length, 83 | ); 84 | }, 85 | ); 86 | } 87 | } 88 | // #enddocregion PostsList 89 | -------------------------------------------------------------------------------- /example/lib/shared/user.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:equatable/equatable.dart'; 18 | 19 | /// A person or entity that creates [Post]s. 20 | class User extends Equatable { 21 | /// Creates a [User]. 22 | const User({ 23 | required this.id, 24 | required this.name, 25 | required this.handle, 26 | }); 27 | 28 | /// This user's ID. 29 | final int id; 30 | 31 | /// This user's full name. 32 | final String name; 33 | 34 | /// This user's @ handle. 35 | final String handle; 36 | 37 | @override 38 | List get props => [id, name, handle]; 39 | 40 | @override 41 | String toString() => handle; 42 | 43 | /// A fake user named "Joe User". 44 | static User joeUser = const User( 45 | id: 1, 46 | name: 'Joe User', 47 | handle: '@joe', 48 | ); 49 | 50 | /// A fake user named "Alice User". 51 | static User aliceUser = const User( 52 | id: 2, 53 | name: 'Alice User', 54 | handle: '@alice', 55 | ); 56 | 57 | /// Creates a [User] with the given id. 58 | static User fakeUser({required int id}) => User( 59 | id: id, 60 | name: 'User $id', 61 | handle: '@user$id', 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /example/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /example/linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This file controls Flutter-level build steps. It should not be edited. 16 | cmake_minimum_required(VERSION 3.10) 17 | 18 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 19 | 20 | # Configuration provided via flutter tool. 21 | include(${EPHEMERAL_DIR}/generated_config.cmake) 22 | 23 | # TODO: Move the rest of this into files in ephemeral. See 24 | # https://github.com/flutter/flutter/issues/57146. 25 | 26 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 27 | # which isn't available in 3.10. 28 | function(list_prepend LIST_NAME PREFIX) 29 | set(NEW_LIST "") 30 | foreach(element ${${LIST_NAME}}) 31 | list(APPEND NEW_LIST "${PREFIX}${element}") 32 | endforeach(element) 33 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 34 | endfunction() 35 | 36 | # === Flutter Library === 37 | # System-level dependencies. 38 | find_package(PkgConfig REQUIRED) 39 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 40 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 41 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 42 | 43 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 44 | 45 | # Published to parent scope for install step. 46 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 47 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 48 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 49 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 50 | 51 | list(APPEND FLUTTER_LIBRARY_HEADERS 52 | "fl_basic_message_channel.h" 53 | "fl_binary_codec.h" 54 | "fl_binary_messenger.h" 55 | "fl_dart_project.h" 56 | "fl_engine.h" 57 | "fl_json_message_codec.h" 58 | "fl_json_method_codec.h" 59 | "fl_message_codec.h" 60 | "fl_method_call.h" 61 | "fl_method_channel.h" 62 | "fl_method_codec.h" 63 | "fl_method_response.h" 64 | "fl_plugin_registrar.h" 65 | "fl_plugin_registry.h" 66 | "fl_standard_message_codec.h" 67 | "fl_standard_method_codec.h" 68 | "fl_string_codec.h" 69 | "fl_value.h" 70 | "fl_view.h" 71 | "flutter_linux.h" 72 | ) 73 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 74 | add_library(flutter INTERFACE) 75 | target_include_directories(flutter INTERFACE 76 | "${EPHEMERAL_DIR}" 77 | ) 78 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 79 | target_link_libraries(flutter INTERFACE 80 | PkgConfig::GTK 81 | PkgConfig::GLIB 82 | PkgConfig::GIO 83 | ) 84 | add_dependencies(flutter flutter_assemble) 85 | 86 | # === Flutter tool backend === 87 | # _phony_ is a non-existent file to force this command to run every time, 88 | # since currently there's no way to get a full input/output list from the 89 | # flutter tool. 90 | add_custom_command( 91 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 92 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 93 | COMMAND ${CMAKE_COMMAND} -E env 94 | ${FLUTTER_TOOL_ENVIRONMENT} 95 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 96 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 97 | VERBATIM 98 | ) 99 | add_custom_target(flutter_assemble DEPENDS 100 | "${FLUTTER_LIBRARY}" 101 | ${FLUTTER_LIBRARY_HEADERS} 102 | ) 103 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // 18 | // Generated file. Do not edit. 19 | // 20 | 21 | // clang-format off 22 | 23 | #include "generated_plugin_registrant.h" 24 | 25 | 26 | void fl_register_plugins(FlPluginRegistry* registry) { 27 | } 28 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // 18 | // Generated file. Do not edit. 19 | // 20 | 21 | // clang-format off 22 | 23 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 24 | #define GENERATED_PLUGIN_REGISTRANT_ 25 | 26 | #include 27 | 28 | // Registers Flutter plugins. 29 | void fl_register_plugins(FlPluginRegistry* registry); 30 | 31 | #endif // GENERATED_PLUGIN_REGISTRANT_ 32 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # 16 | # Generated file, do not edit. 17 | # 18 | 19 | list(APPEND FLUTTER_PLUGIN_LIST 20 | ) 21 | 22 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 23 | ) 24 | 25 | set(PLUGIN_BUNDLED_LIBRARIES) 26 | 27 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 28 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 29 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 30 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 31 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 32 | endforeach(plugin) 33 | 34 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 35 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 36 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 37 | endforeach(ffi_plugin) 38 | -------------------------------------------------------------------------------- /example/linux/main.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "my_application.h" 18 | 19 | int main(int argc, char** argv) { 20 | g_autoptr(MyApplication) app = my_application_new(); 21 | return g_application_run(G_APPLICATION(app), argc, argv); 22 | } 23 | -------------------------------------------------------------------------------- /example/linux/my_application.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "my_application.h" 18 | 19 | #include 20 | #ifdef GDK_WINDOWING_X11 21 | #include 22 | #endif 23 | 24 | #include "flutter/generated_plugin_registrant.h" 25 | 26 | struct _MyApplication { 27 | GtkApplication parent_instance; 28 | char** dart_entrypoint_arguments; 29 | }; 30 | 31 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 32 | 33 | // Implements GApplication::activate. 34 | static void my_application_activate(GApplication* application) { 35 | MyApplication* self = MY_APPLICATION(application); 36 | GtkWindow* window = 37 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 38 | 39 | // Use a header bar when running in GNOME as this is the common style used 40 | // by applications and is the setup most users will be using (e.g. Ubuntu 41 | // desktop). 42 | // If running on X and not using GNOME then just use a traditional title bar 43 | // in case the window manager does more exotic layout, e.g. tiling. 44 | // If running on Wayland assume the header bar will work (may need changing 45 | // if future cases occur). 46 | gboolean use_header_bar = TRUE; 47 | #ifdef GDK_WINDOWING_X11 48 | GdkScreen* screen = gtk_window_get_screen(window); 49 | if (GDK_IS_X11_SCREEN(screen)) { 50 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 51 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 52 | use_header_bar = FALSE; 53 | } 54 | } 55 | #endif 56 | if (use_header_bar) { 57 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 58 | gtk_widget_show(GTK_WIDGET(header_bar)); 59 | gtk_header_bar_set_title(header_bar, "example"); 60 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 61 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 62 | } else { 63 | gtk_window_set_title(window, "example"); 64 | } 65 | 66 | gtk_window_set_default_size(window, 1280, 720); 67 | gtk_widget_show(GTK_WIDGET(window)); 68 | 69 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 70 | fl_dart_project_set_dart_entrypoint_arguments( 71 | project, self->dart_entrypoint_arguments); 72 | 73 | FlView* view = fl_view_new(project); 74 | gtk_widget_show(GTK_WIDGET(view)); 75 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 76 | 77 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 78 | 79 | gtk_widget_grab_focus(GTK_WIDGET(view)); 80 | } 81 | 82 | // Implements GApplication::local_command_line. 83 | static gboolean my_application_local_command_line(GApplication* application, 84 | gchar*** arguments, 85 | int* exit_status) { 86 | MyApplication* self = MY_APPLICATION(application); 87 | // Strip out the first argument as it is the binary name. 88 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 89 | 90 | g_autoptr(GError) error = nullptr; 91 | if (!g_application_register(application, nullptr, &error)) { 92 | g_warning("Failed to register: %s", error->message); 93 | *exit_status = 1; 94 | return TRUE; 95 | } 96 | 97 | g_application_activate(application); 98 | *exit_status = 0; 99 | 100 | return TRUE; 101 | } 102 | 103 | // Implements GObject::dispose. 104 | static void my_application_dispose(GObject* object) { 105 | MyApplication* self = MY_APPLICATION(object); 106 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 107 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 108 | } 109 | 110 | static void my_application_class_init(MyApplicationClass* klass) { 111 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 112 | G_APPLICATION_CLASS(klass)->local_command_line = 113 | my_application_local_command_line; 114 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 115 | } 116 | 117 | static void my_application_init(MyApplication* self) {} 118 | 119 | MyApplication* my_application_new() { 120 | return MY_APPLICATION(g_object_new(my_application_get_type(), 121 | "application-id", APPLICATION_ID, "flags", 122 | G_APPLICATION_NON_UNIQUE, nullptr)); 123 | } 124 | -------------------------------------------------------------------------------- /example/linux/my_application.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef FLUTTER_MY_APPLICATION_H_ 18 | #define FLUTTER_MY_APPLICATION_H_ 19 | 20 | #include 21 | 22 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 23 | GtkApplication) 24 | 25 | /** 26 | * my_application_new: 27 | * 28 | * Creates a new Flutter-based application. 29 | * 30 | * Returns: a new #MyApplication. 31 | */ 32 | MyApplication* my_application_new(); 33 | 34 | #endif // FLUTTER_MY_APPLICATION_H_ 35 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // 18 | // Generated file. Do not edit. 19 | // 20 | 21 | import FlutterMacOS 22 | import Foundation 23 | 24 | 25 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 26 | } 27 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Cocoa 18 | import FlutterMacOS 19 | 20 | @NSApplicationMain 21 | class AppDelegate: FlutterAppDelegate { 22 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 23 | return true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Cocoa 18 | import FlutterMacOS 19 | 20 | class MainFlutterWindow: NSWindow { 21 | override func awakeFromNib() { 22 | let flutterViewController = FlutterViewController.init() 23 | let windowFrame = self.frame 24 | self.contentViewController = flutterViewController 25 | self.setFrame(windowFrame, display: true) 26 | 27 | RegisterGeneratedPlugins(registry: flutterViewController) 28 | 29 | super.awakeFromNib() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: stager_example 16 | description: Demonstrates the Stager package. 17 | 18 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 19 | 20 | version: 1.0.0+1 21 | 22 | environment: 23 | sdk: ">=2.17.0 <3.0.0" 24 | 25 | dependencies: 26 | equatable: ^2.0.5 27 | flutter: 28 | sdk: flutter 29 | mockito: ^5.3.2 30 | provider: ^6.0.5 31 | stager: 32 | git: 33 | url: git@github.com:bryanoltman/stager.git 34 | ref: main 35 | 36 | dev_dependencies: 37 | build_runner: ^2.3.3 38 | flutter_lints: ^2.0.1 39 | flutter_test: 40 | sdk: flutter 41 | 42 | dependency_overrides: 43 | stager: 44 | path: ../ 45 | 46 | flutter: 47 | uses-material-design: true 48 | -------------------------------------------------------------------------------- /example/test/pages/post_detail_page_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | import 'package:flutter_test/flutter_test.dart'; 19 | import 'package:stager/stager.dart'; 20 | import 'package:stager_example/pages/posts_list/posts_list_page_scenes.dart'; 21 | 22 | import '../scene_test_extensions.dart'; 23 | 24 | void main() { 25 | testWidgets('shows a loading state', (WidgetTester tester) async { 26 | final Scene scene = LoadingScene(); 27 | await scene.setUpAndPump(tester); 28 | expect(find.byType(CircularProgressIndicator), findsOneWidget); 29 | }); 30 | 31 | testWidgets('shows an error state', (WidgetTester tester) async { 32 | final Scene scene = ErrorScene(); 33 | await scene.setUpAndPump(tester); 34 | expect(find.text('Error'), findsOneWidget); 35 | }); 36 | 37 | testWidgets('shows an empty state', (WidgetTester tester) async { 38 | final Scene scene = EmptyListScene(); 39 | await scene.setUpAndPump(tester); 40 | expect(find.text('No posts'), findsOneWidget); 41 | }); 42 | 43 | testWidgets('shows posts', (WidgetTester tester) async { 44 | final Scene scene = WithPostsScene(); 45 | await scene.setUpAndPump(tester); 46 | expect(find.text('Post 1'), findsOneWidget); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /example/test/pages/posts_list_page_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | import 'package:flutter_test/flutter_test.dart'; 19 | import 'package:stager/stager.dart'; 20 | import 'package:stager_example/pages/posts_list/posts_list_page_scenes.dart'; 21 | 22 | import '../scene_test_extensions.dart'; 23 | 24 | void main() { 25 | testWidgets('shows a loading state', (WidgetTester tester) async { 26 | final Scene scene = LoadingScene(); 27 | await scene.setUpAndPump(tester); 28 | expect(find.byType(CircularProgressIndicator), findsOneWidget); 29 | }); 30 | 31 | testWidgets('shows an error state', (WidgetTester tester) async { 32 | final Scene scene = ErrorScene(); 33 | await scene.setUpAndPump(tester); 34 | expect(find.text('Error'), findsOneWidget); 35 | }); 36 | 37 | // #docregion EmptySceneTest 38 | testWidgets('shows an empty state', (WidgetTester tester) async { 39 | final Scene scene = EmptyListScene(); 40 | await scene.setUpAndPump(tester); 41 | expect(find.text('No posts'), findsOneWidget); 42 | }); 43 | // #enddocregion EmptySceneTest 44 | 45 | testWidgets('shows posts', (WidgetTester tester) async { 46 | final Scene scene = WithPostsScene(); 47 | await scene.setUpAndPump(tester); 48 | expect(find.text('Post 1'), findsOneWidget); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /example/test/pages/user_detail_page_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | import 'package:flutter_test/flutter_test.dart'; 19 | import 'package:stager/stager.dart'; 20 | import 'package:stager_example/pages/user_detail/user_detail_page_scenes.dart'; 21 | 22 | import '../scene_test_extensions.dart'; 23 | 24 | void main() { 25 | testWidgets('shows a loading state', (WidgetTester tester) async { 26 | final Scene scene = LoadingUserDetailPageScene(); 27 | await scene.setUpAndPump(tester); 28 | expect(find.byType(CircularProgressIndicator), findsOneWidget); 29 | }); 30 | 31 | testWidgets('shows an error state', (WidgetTester tester) async { 32 | final Scene scene = ErrorUserDetailPageScene(); 33 | await scene.setUpAndPump(tester); 34 | expect(find.text('Error'), findsOneWidget); 35 | }); 36 | 37 | testWidgets('shows an empty state', (WidgetTester tester) async { 38 | final Scene scene = EmptyUserDetailPageScene(); 39 | await scene.setUpAndPump(tester); 40 | expect(find.text('No posts'), findsOneWidget); 41 | }); 42 | 43 | testWidgets('shows posts', (WidgetTester tester) async { 44 | final Scene scene = WithPostsUserDetailPageScene(); 45 | await scene.setUpAndPump(tester); 46 | expect(find.text('Post 1'), findsOneWidget); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /example/test/scene_test_extensions.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | import 'package:flutter_test/flutter_test.dart'; 19 | import 'package:stager/stager.dart'; 20 | 21 | /// Convenience functions for widget testing [Scene]s. 22 | /// 23 | /// TODO: move this to a new stager_testing package 24 | extension Testing on Scene { 25 | /// A convenience function that prepares a [Scene] for widget testing. 26 | /// 27 | /// Calls [Scene.setUp] with either [environmentState] or a default 28 | /// [EnvironmentState] if none is provided and provides [Scene.build] with a 29 | /// valid BuildContext. 30 | Future setUpAndPump(WidgetTester tester) async { 31 | await this.setUp(); 32 | 33 | await tester.pumpWidget( 34 | Builder( 35 | builder: (BuildContext context) => build(context), 36 | ), 37 | ); 38 | 39 | await tester.pump(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | example 34 | 35 | 36 | 40 | 41 | 42 | 43 | 44 | 45 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Project-level configuration. 16 | cmake_minimum_required(VERSION 3.14) 17 | project(example LANGUAGES CXX) 18 | 19 | # The name of the executable created for the application. Change this to change 20 | # the on-disk name of your application. 21 | set(BINARY_NAME "example") 22 | 23 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 24 | # versions of CMake. 25 | cmake_policy(SET CMP0063 NEW) 26 | 27 | # Define build configuration option. 28 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 29 | if(IS_MULTICONFIG) 30 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 31 | CACHE STRING "" FORCE) 32 | else() 33 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 34 | set(CMAKE_BUILD_TYPE "Debug" CACHE 35 | STRING "Flutter build mode" FORCE) 36 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 37 | "Debug" "Profile" "Release") 38 | endif() 39 | endif() 40 | # Define settings for the Profile build mode. 41 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 42 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 43 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 44 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 45 | 46 | # Use Unicode for all projects. 47 | add_definitions(-DUNICODE -D_UNICODE) 48 | 49 | # Compilation settings that should be applied to most targets. 50 | # 51 | # Be cautious about adding new options here, as plugins use this function by 52 | # default. In most cases, you should add new options to specific targets instead 53 | # of modifying this function. 54 | function(APPLY_STANDARD_SETTINGS TARGET) 55 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 56 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 57 | target_compile_options(${TARGET} PRIVATE /EHsc) 58 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 59 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 60 | endfunction() 61 | 62 | # Flutter library and tool build rules. 63 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 64 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 65 | 66 | # Application build; see runner/CMakeLists.txt. 67 | add_subdirectory("runner") 68 | 69 | # Generated plugin build rules, which manage building the plugins and adding 70 | # them to the application. 71 | include(flutter/generated_plugins.cmake) 72 | 73 | 74 | # === Installation === 75 | # Support files are copied into place next to the executable, so that it can 76 | # run in place. This is done instead of making a separate bundle (as on Linux) 77 | # so that building and running from within Visual Studio will work. 78 | set(BUILD_BUNDLE_DIR "$") 79 | # Make the "install" step default, as it's required to run. 80 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 81 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 82 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 83 | endif() 84 | 85 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 86 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 87 | 88 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 89 | COMPONENT Runtime) 90 | 91 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 92 | COMPONENT Runtime) 93 | 94 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 95 | COMPONENT Runtime) 96 | 97 | if(PLUGIN_BUNDLED_LIBRARIES) 98 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 99 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 100 | COMPONENT Runtime) 101 | endif() 102 | 103 | # Fully re-copy the assets directory on each build to avoid having stale files 104 | # from a previous install. 105 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 106 | install(CODE " 107 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 108 | " COMPONENT Runtime) 109 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 110 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 111 | 112 | # Install the AOT library on non-Debug builds only. 113 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 114 | CONFIGURATIONS Profile;Release 115 | COMPONENT Runtime) 116 | -------------------------------------------------------------------------------- /example/windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This file controls Flutter-level build steps. It should not be edited. 16 | cmake_minimum_required(VERSION 3.14) 17 | 18 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 19 | 20 | # Configuration provided via flutter tool. 21 | include(${EPHEMERAL_DIR}/generated_config.cmake) 22 | 23 | # TODO: Move the rest of this into files in ephemeral. See 24 | # https://github.com/flutter/flutter/issues/57146. 25 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 26 | 27 | # === Flutter Library === 28 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 29 | 30 | # Published to parent scope for install step. 31 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 32 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 33 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 34 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 35 | 36 | list(APPEND FLUTTER_LIBRARY_HEADERS 37 | "flutter_export.h" 38 | "flutter_windows.h" 39 | "flutter_messenger.h" 40 | "flutter_plugin_registrar.h" 41 | "flutter_texture_registrar.h" 42 | ) 43 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 44 | add_library(flutter INTERFACE) 45 | target_include_directories(flutter INTERFACE 46 | "${EPHEMERAL_DIR}" 47 | ) 48 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 49 | add_dependencies(flutter flutter_assemble) 50 | 51 | # === Wrapper === 52 | list(APPEND CPP_WRAPPER_SOURCES_CORE 53 | "core_implementations.cc" 54 | "standard_codec.cc" 55 | ) 56 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 57 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 58 | "plugin_registrar.cc" 59 | ) 60 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 61 | list(APPEND CPP_WRAPPER_SOURCES_APP 62 | "flutter_engine.cc" 63 | "flutter_view_controller.cc" 64 | ) 65 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 66 | 67 | # Wrapper sources needed for a plugin. 68 | add_library(flutter_wrapper_plugin STATIC 69 | ${CPP_WRAPPER_SOURCES_CORE} 70 | ${CPP_WRAPPER_SOURCES_PLUGIN} 71 | ) 72 | apply_standard_settings(flutter_wrapper_plugin) 73 | set_target_properties(flutter_wrapper_plugin PROPERTIES 74 | POSITION_INDEPENDENT_CODE ON) 75 | set_target_properties(flutter_wrapper_plugin PROPERTIES 76 | CXX_VISIBILITY_PRESET hidden) 77 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 78 | target_include_directories(flutter_wrapper_plugin PUBLIC 79 | "${WRAPPER_ROOT}/include" 80 | ) 81 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 82 | 83 | # Wrapper sources needed for the runner. 84 | add_library(flutter_wrapper_app STATIC 85 | ${CPP_WRAPPER_SOURCES_CORE} 86 | ${CPP_WRAPPER_SOURCES_APP} 87 | ) 88 | apply_standard_settings(flutter_wrapper_app) 89 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 90 | target_include_directories(flutter_wrapper_app PUBLIC 91 | "${WRAPPER_ROOT}/include" 92 | ) 93 | add_dependencies(flutter_wrapper_app flutter_assemble) 94 | 95 | # === Flutter tool backend === 96 | # _phony_ is a non-existent file to force this command to run every time, 97 | # since currently there's no way to get a full input/output list from the 98 | # flutter tool. 99 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 100 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 101 | add_custom_command( 102 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 103 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 104 | ${CPP_WRAPPER_SOURCES_APP} 105 | ${PHONY_OUTPUT} 106 | COMMAND ${CMAKE_COMMAND} -E env 107 | ${FLUTTER_TOOL_ENVIRONMENT} 108 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 109 | windows-x64 $ 110 | VERBATIM 111 | ) 112 | add_custom_target(flutter_assemble DEPENDS 113 | "${FLUTTER_LIBRARY}" 114 | ${FLUTTER_LIBRARY_HEADERS} 115 | ${CPP_WRAPPER_SOURCES_CORE} 116 | ${CPP_WRAPPER_SOURCES_PLUGIN} 117 | ${CPP_WRAPPER_SOURCES_APP} 118 | ) 119 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // 18 | // Generated file. Do not edit. 19 | // 20 | 21 | // clang-format off 22 | 23 | #include "generated_plugin_registrant.h" 24 | 25 | 26 | void RegisterPlugins(flutter::PluginRegistry* registry) { 27 | } 28 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // 18 | // Generated file. Do not edit. 19 | // 20 | 21 | // clang-format off 22 | 23 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 24 | #define GENERATED_PLUGIN_REGISTRANT_ 25 | 26 | #include 27 | 28 | // Registers Flutter plugins. 29 | void RegisterPlugins(flutter::PluginRegistry* registry); 30 | 31 | #endif // GENERATED_PLUGIN_REGISTRANT_ 32 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # 16 | # Generated file, do not edit. 17 | # 18 | 19 | list(APPEND FLUTTER_PLUGIN_LIST 20 | ) 21 | 22 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 23 | ) 24 | 25 | set(PLUGIN_BUNDLED_LIBRARIES) 26 | 27 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 28 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 29 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 30 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 31 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 32 | endforeach(plugin) 33 | 34 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 35 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 36 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 37 | endforeach(ffi_plugin) 38 | -------------------------------------------------------------------------------- /example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | cmake_minimum_required(VERSION 3.14) 16 | project(runner LANGUAGES CXX) 17 | 18 | # Define the application target. To change its name, change BINARY_NAME in the 19 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 20 | # work. 21 | # 22 | # Any new source files that you add to the application should be added here. 23 | add_executable(${BINARY_NAME} WIN32 24 | "flutter_window.cpp" 25 | "main.cpp" 26 | "utils.cpp" 27 | "win32_window.cpp" 28 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 29 | "Runner.rc" 30 | "runner.exe.manifest" 31 | ) 32 | 33 | # Apply the standard set of build settings. This can be removed for applications 34 | # that need different build settings. 35 | apply_standard_settings(${BINARY_NAME}) 36 | 37 | # Add preprocessor definitions for the build version. 38 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 39 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 40 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 41 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 42 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 43 | 44 | # Disable Windows macros that collide with C++ standard library functions. 45 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 46 | 47 | # Add dependency libraries and include directories. Add any application-specific 48 | # dependencies here. 49 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 50 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 51 | 52 | # Run the Flutter tool portions of the build. This must not be removed. 53 | add_dependencies(${BINARY_NAME} flutter_assemble) 54 | -------------------------------------------------------------------------------- /example/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "example" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "example" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "example.exe" "\0" 98 | VALUE "ProductName", "example" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "flutter_window.h" 18 | 19 | #include 20 | 21 | #include "flutter/generated_plugin_registrant.h" 22 | 23 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 24 | : project_(project) {} 25 | 26 | FlutterWindow::~FlutterWindow() {} 27 | 28 | bool FlutterWindow::OnCreate() { 29 | if (!Win32Window::OnCreate()) { 30 | return false; 31 | } 32 | 33 | RECT frame = GetClientArea(); 34 | 35 | // The size here must match the window dimensions to avoid unnecessary surface 36 | // creation / destruction in the startup path. 37 | flutter_controller_ = std::make_unique( 38 | frame.right - frame.left, frame.bottom - frame.top, project_); 39 | // Ensure that basic setup of the controller was successful. 40 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 41 | return false; 42 | } 43 | RegisterPlugins(flutter_controller_->engine()); 44 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 45 | return true; 46 | } 47 | 48 | void FlutterWindow::OnDestroy() { 49 | if (flutter_controller_) { 50 | flutter_controller_ = nullptr; 51 | } 52 | 53 | Win32Window::OnDestroy(); 54 | } 55 | 56 | LRESULT 57 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 58 | WPARAM const wparam, 59 | LPARAM const lparam) noexcept { 60 | // Give Flutter, including plugins, an opportunity to handle window messages. 61 | if (flutter_controller_) { 62 | std::optional result = 63 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 64 | lparam); 65 | if (result) { 66 | return *result; 67 | } 68 | } 69 | 70 | switch (message) { 71 | case WM_FONTCHANGE: 72 | flutter_controller_->engine()->ReloadSystemFonts(); 73 | break; 74 | } 75 | 76 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 77 | } 78 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 18 | #define RUNNER_FLUTTER_WINDOW_H_ 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include "win32_window.h" 26 | 27 | // A window that does nothing but host a Flutter view. 28 | class FlutterWindow : public Win32Window { 29 | public: 30 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 31 | explicit FlutterWindow(const flutter::DartProject& project); 32 | virtual ~FlutterWindow(); 33 | 34 | protected: 35 | // Win32Window: 36 | bool OnCreate() override; 37 | void OnDestroy() override; 38 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 39 | LPARAM const lparam) noexcept override; 40 | 41 | private: 42 | // The project to run. 43 | flutter::DartProject project_; 44 | 45 | // The Flutter instance hosted by this window. 46 | std::unique_ptr flutter_controller_; 47 | }; 48 | 49 | #endif // RUNNER_FLUTTER_WINDOW_H_ 50 | -------------------------------------------------------------------------------- /example/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "flutter_window.h" 22 | #include "utils.h" 23 | 24 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 25 | _In_ wchar_t* command_line, _In_ int show_command) { 26 | // Attach to console when present (e.g., 'flutter run') or create a 27 | // new console when running with a debugger. 28 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 29 | CreateAndAttachConsole(); 30 | } 31 | 32 | // Initialize COM, so that it is available for use in the library and/or 33 | // plugins. 34 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 35 | 36 | flutter::DartProject project(L"data"); 37 | 38 | std::vector command_line_arguments = GetCommandLineArguments(); 39 | 40 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 41 | 42 | FlutterWindow window(project); 43 | Win32Window::Point origin(10, 10); 44 | Win32Window::Size size(1280, 720); 45 | if (!window.CreateAndShow(L"example", origin, size)) { 46 | return EXIT_FAILURE; 47 | } 48 | window.SetQuitOnClose(true); 49 | 50 | ::MSG msg; 51 | while (::GetMessage(&msg, nullptr, 0, 0)) { 52 | ::TranslateMessage(&msg); 53 | ::DispatchMessage(&msg); 54 | } 55 | 56 | ::CoUninitialize(); 57 | return EXIT_SUCCESS; 58 | } 59 | -------------------------------------------------------------------------------- /example/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | //{{NO_DEPENDENCIES}} 18 | // Microsoft Visual C++ generated include file. 19 | // Used by Runner.rc 20 | // 21 | #define IDI_APP_ICON 101 22 | 23 | // Next default values for new objects 24 | // 25 | #ifdef APSTUDIO_INVOKED 26 | #ifndef APSTUDIO_READONLY_SYMBOLS 27 | #define _APS_NEXT_RESOURCE_VALUE 102 28 | #define _APS_NEXT_COMMAND_VALUE 40001 29 | #define _APS_NEXT_CONTROL_VALUE 1001 30 | #define _APS_NEXT_SYMED_VALUE 101 31 | #endif 32 | #endif 33 | -------------------------------------------------------------------------------- /example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/flutter-stager/2e9c5de4e14d0b0d2e0980101d7429e3440fc6cc/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "utils.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | void CreateAndAttachConsole() { 27 | if (::AllocConsole()) { 28 | FILE* unused; 29 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 30 | _dup2(_fileno(stdout), 1); 31 | } 32 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 33 | _dup2(_fileno(stdout), 2); 34 | } 35 | std::ios::sync_with_stdio(); 36 | FlutterDesktopResyncOutputStreams(); 37 | } 38 | } 39 | 40 | std::vector GetCommandLineArguments() { 41 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 42 | int argc; 43 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 44 | if (argv == nullptr) { 45 | return std::vector(); 46 | } 47 | 48 | std::vector command_line_arguments; 49 | 50 | // Skip the first argument as it's the binary name. 51 | for (int i = 1; i < argc; i++) { 52 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 53 | } 54 | 55 | ::LocalFree(argv); 56 | 57 | return command_line_arguments; 58 | } 59 | 60 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 61 | if (utf16_string == nullptr) { 62 | return std::string(); 63 | } 64 | int target_length = 65 | ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, 66 | nullptr, 0, nullptr, nullptr); 67 | std::string utf8_string; 68 | if (target_length == 0 || target_length > utf8_string.max_size()) { 69 | return utf8_string; 70 | } 71 | utf8_string.resize(target_length); 72 | int converted_length = ::WideCharToMultiByte( 73 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(), 74 | target_length, nullptr, nullptr); 75 | if (converted_length == 0) { 76 | return std::string(); 77 | } 78 | return utf8_string; 79 | } 80 | -------------------------------------------------------------------------------- /example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef RUNNER_UTILS_H_ 18 | #define RUNNER_UTILS_H_ 19 | 20 | #include 21 | #include 22 | 23 | // Creates a console for the process, and redirects stdout and stderr to 24 | // it for both the runner and the Flutter library. 25 | void CreateAndAttachConsole(); 26 | 27 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 28 | // encoded in UTF-8. Returns an empty std::string on failure. 29 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 30 | 31 | // Gets the command line arguments passed in as a std::vector, 32 | // encoded in UTF-8. Returns an empty std::vector on failure. 33 | std::vector GetCommandLineArguments(); 34 | 35 | #endif // RUNNER_UTILS_H_ 36 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef RUNNER_WIN32_WINDOW_H_ 18 | #define RUNNER_WIN32_WINDOW_H_ 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 27 | // inherited from by classes that wish to specialize with custom 28 | // rendering and input handling 29 | class Win32Window { 30 | public: 31 | struct Point { 32 | unsigned int x; 33 | unsigned int y; 34 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 35 | }; 36 | 37 | struct Size { 38 | unsigned int width; 39 | unsigned int height; 40 | Size(unsigned int width, unsigned int height) 41 | : width(width), height(height) {} 42 | }; 43 | 44 | Win32Window(); 45 | virtual ~Win32Window(); 46 | 47 | // Creates and shows a win32 window with |title| and position and size using 48 | // |origin| and |size|. New windows are created on the default monitor. Window 49 | // sizes are specified to the OS in physical pixels, hence to ensure a 50 | // consistent size to will treat the width height passed in to this function 51 | // as logical pixels and scale to appropriate for the default monitor. Returns 52 | // true if the window was created successfully. 53 | bool CreateAndShow(const std::wstring& title, const Point& origin, 54 | const Size& size); 55 | 56 | // Release OS resources associated with window. 57 | void Destroy(); 58 | 59 | // Inserts |content| into the window tree. 60 | void SetChildContent(HWND content); 61 | 62 | // Returns the backing Window handle to enable clients to set icon and other 63 | // window properties. Returns nullptr if the window has been destroyed. 64 | HWND GetHandle(); 65 | 66 | // If true, closing this window will quit the application. 67 | void SetQuitOnClose(bool quit_on_close); 68 | 69 | // Return a RECT representing the bounds of the current client area. 70 | RECT GetClientArea(); 71 | 72 | protected: 73 | // Processes and route salient window messages for mouse handling, 74 | // size change and DPI. Delegates handling of these to member overloads that 75 | // inheriting classes can handle. 76 | virtual LRESULT MessageHandler(HWND window, UINT const message, 77 | WPARAM const wparam, 78 | LPARAM const lparam) noexcept; 79 | 80 | // Called when CreateAndShow is called, allowing subclass window-related 81 | // setup. Subclasses should return false if setup fails. 82 | virtual bool OnCreate(); 83 | 84 | // Called when Destroy is called. 85 | virtual void OnDestroy(); 86 | 87 | private: 88 | friend class WindowClassRegistrar; 89 | 90 | // OS callback called by message pump. Handles the WM_NCCREATE message which 91 | // is passed when the non-client area is being created and enables automatic 92 | // non-client DPI scaling so that the non-client area automatically 93 | // responsponds to changes in DPI. All other messages are handled by 94 | // MessageHandler. 95 | static LRESULT CALLBACK WndProc(HWND const window, UINT const message, 96 | WPARAM const wparam, 97 | LPARAM const lparam) noexcept; 98 | 99 | // Retrieves a class instance pointer for |window| 100 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 101 | 102 | bool quit_on_close_ = false; 103 | 104 | // window handle for top level window. 105 | HWND window_handle_ = nullptr; 106 | 107 | // window handle for hosted content. 108 | HWND child_content_ = nullptr; 109 | }; 110 | 111 | #endif // RUNNER_WIN32_WINDOW_H_ 112 | -------------------------------------------------------------------------------- /lib/builder.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:build/build.dart'; 18 | import 'package:source_gen/source_gen.dart'; 19 | 20 | import 'src/stager_app_generator.dart'; 21 | 22 | /// Generates a .stager_app.dart file with a runnable main that calls [runApp] 23 | /// with a [StagerApp] displaying all titled [Scene]s. 24 | Builder buildStagerApp(BuilderOptions options) => LibraryBuilder( 25 | StagerAppGenerator(), 26 | generatedExtension: '.stager_app.g.dart', 27 | ); 28 | -------------------------------------------------------------------------------- /lib/src/environment/controls/boolean_control.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../environment_control_panel.dart'; 20 | import 'environment_control.dart'; 21 | 22 | /// An [EnvironmentControl] for the manipulation of a boolean [EnvironmentState] 23 | /// variable. 24 | class BooleanControl extends EnvironmentControl { 25 | /// An [EnvironmentControl] for the manipulation of a boolean [EnvironmentState] 26 | /// variable. 27 | BooleanControl({ 28 | required super.title, 29 | required super.stateKey, 30 | required super.defaultValue, 31 | super.environmentState, 32 | }); 33 | 34 | @override 35 | Widget builder( 36 | BuildContext context, 37 | bool? currentValue, 38 | void Function(bool) updateValue, 39 | ) { 40 | return Row( 41 | key: ValueKey(stateKey), 42 | children: [ 43 | SizedBox( 44 | width: EnvironmentControlPanel.labelWidth, 45 | child: Text(title), 46 | ), 47 | Switch( 48 | value: currentValue ?? false, 49 | onChanged: updateValue, 50 | ), 51 | ], 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/environment/controls/dropdown_control.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../environment_control_panel.dart'; 20 | import 'environment_control.dart'; 21 | 22 | /// An [EnvironmentControl] that allows the user to choose from one of several 23 | /// options. 24 | class DropdownControl extends EnvironmentControl { 25 | /// An [EnvironmentControl] that allows the user to choose from one of several 26 | /// options. 27 | DropdownControl({ 28 | required super.title, 29 | required super.stateKey, 30 | required super.defaultValue, 31 | required this.items, 32 | super.onValueUpdated, 33 | this.itemTitleBuilder, 34 | super.environmentState, 35 | }); 36 | 37 | /// A list of all available items. 38 | final List items; 39 | 40 | String _titleForItem(T? item) { 41 | if (itemTitleBuilder != null) { 42 | return itemTitleBuilder!(item); 43 | } 44 | 45 | return item.toString(); 46 | } 47 | 48 | /// Used to customize the text of [items] in the drop down. Use this when 49 | /// overriding [T.toString()] is not possible or not desirable. 50 | final String Function(T?)? itemTitleBuilder; 51 | 52 | @override 53 | Widget builder( 54 | BuildContext context, 55 | T? currentValue, 56 | void Function(T) updateValue, 57 | ) { 58 | return Row( 59 | key: ValueKey(stateKey), 60 | children: [ 61 | SizedBox( 62 | width: EnvironmentControlPanel.labelWidth, 63 | child: Text(title), 64 | ), 65 | Expanded( 66 | child: DropdownButton( 67 | isExpanded: true, 68 | value: currentValue, 69 | items: items 70 | .map( 71 | (T? e) => DropdownMenuItem( 72 | value: e, 73 | child: Text(_titleForItem(e)), 74 | ), 75 | ) 76 | .toList(), 77 | onChanged: (T? newValue) { 78 | final T updatedValue = newValue ?? defaultValue; 79 | updateValue(updatedValue); 80 | }, 81 | ), 82 | ), 83 | ], 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/environment/controls/environment_control.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/widgets.dart'; 18 | 19 | import '../state/environment_state.dart'; 20 | 21 | /// Signature for a function that creates an EnvironmentState-backed widget. 22 | typedef EnvironmentControlBuilder = Widget Function( 23 | BuildContext context, 24 | T? currentValue, 25 | Function(T) updateValue, 26 | ); 27 | 28 | /// Provides a widget that allows interaction with an [EnvironmentState]-backed 29 | /// variable. 30 | abstract class EnvironmentControl { 31 | /// Provides a widget that allows interaction with an [EnvironmentState]-backed 32 | /// variable. 33 | EnvironmentControl({ 34 | required this.title, 35 | required this.stateKey, 36 | required this.defaultValue, 37 | this.onValueUpdated, 38 | EnvironmentState? environmentState, 39 | }) : _environmentState = environmentState ?? EnvironmentState.instance { 40 | _environmentState.setDefault(stateKey, defaultValue); 41 | } 42 | 43 | /// An optional callback to perform custom logic when the value backing this 44 | /// control is updated. 45 | final void Function(T)? onValueUpdated; 46 | 47 | /// The name of this control. 48 | final String title; 49 | 50 | /// The key used to store the associated value in [EnvironmentState]. The same 51 | /// key can be used by multiple [EnvironmentControls] to manipulate the same 52 | /// value. 53 | final String stateKey; 54 | 55 | /// The starting value associated with [stateKey]. The [EnvironmentState] 56 | /// value associated with [stateKey] will be reset to this value when 57 | /// [EnvironmentState.reset] is executed. 58 | final T defaultValue; 59 | 60 | /// Builds a widget that presents and manipulates the value of the environment 61 | /// variable keyed by [stateKey]. 62 | Widget builder( 63 | BuildContext context, 64 | T? currentValue, 65 | void Function(T) updateValue, 66 | ); 67 | 68 | /// The data store backing this control. 69 | final EnvironmentState _environmentState; 70 | 71 | /// A convenience function to update the value keyed by [stateKey] in 72 | /// [_environmentState]. 73 | void updateValue(T newValue) { 74 | _environmentState.set(stateKey, newValue); 75 | if (onValueUpdated != null) { 76 | onValueUpdated!(newValue); 77 | } 78 | } 79 | 80 | /// The value of the environment variable keyed by [stateKey]. 81 | T get currentValue => _environmentState.get(stateKey) ?? defaultValue; 82 | 83 | /// Invokes [builder] with the current value keyed by [stateKey] and a 84 | /// callback to update that value. 85 | Widget build(BuildContext context) => 86 | builder(context, currentValue, updateValue); 87 | } 88 | -------------------------------------------------------------------------------- /lib/src/environment/controls/stepper_control.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../environment_control_panel.dart'; 20 | import 'environment_control.dart'; 21 | 22 | /// An [EnvironmentControlPanel] widget that allows a user to decrease and 23 | /// increase a value. 24 | class StepperControl extends EnvironmentControl { 25 | /// An [EnvironmentControlPanel] widget that allows a user to decrease and 26 | /// increase a value. 27 | StepperControl({ 28 | required super.title, 29 | required super.stateKey, 30 | required super.defaultValue, 31 | required this.onDecrementPressed, 32 | required this.onIncrementPressed, 33 | super.onValueUpdated, 34 | this.displayValueBuilder, 35 | super.environmentState, 36 | }); 37 | 38 | /// Executed when the increment button is pressed. 39 | final T Function(T currentValue) onDecrementPressed; 40 | 41 | /// Executed when the decrement button is pressed. 42 | final T Function(T currentValue) onIncrementPressed; 43 | 44 | /// Returns a presentable String representation of [currentValue]. 45 | final String Function(T currentValue)? displayValueBuilder; 46 | 47 | String get _displayValue { 48 | if (displayValueBuilder != null) { 49 | return displayValueBuilder!(currentValue!); 50 | } 51 | 52 | return currentValue.toString(); 53 | } 54 | 55 | @override 56 | Widget builder( 57 | BuildContext context, 58 | T? currentValue, 59 | void Function(T) updateValue, 60 | ) { 61 | return Row( 62 | key: ValueKey(stateKey), 63 | children: [ 64 | SizedBox( 65 | width: EnvironmentControlPanel.labelWidth, 66 | child: Text(title), 67 | ), 68 | IconButton( 69 | // ignore: null_check_on_nullable_type_parameter 70 | onPressed: () => updateValue(onDecrementPressed(currentValue!)), 71 | icon: const Icon(Icons.remove), 72 | ), 73 | Text(_displayValue), 74 | IconButton( 75 | // ignore: null_check_on_nullable_type_parameter 76 | onPressed: () => updateValue(onIncrementPressed(currentValue!)), 77 | icon: const Icon(Icons.add), 78 | ), 79 | ], 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/src/environment/controls/text_input_control.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../environment_control_panel.dart'; 20 | import 'environment_control.dart'; 21 | 22 | /// A control to manipulate String [EnvironmentState] values. 23 | class TextInputControl extends EnvironmentControl { 24 | /// A control to manipulate String [EnvironmentState] values. 25 | TextInputControl({ 26 | required super.title, 27 | required super.stateKey, 28 | required super.defaultValue, 29 | super.onValueUpdated, 30 | super.environmentState, 31 | }); 32 | 33 | final FocusNode _focusNode = FocusNode(); 34 | final TextEditingController _textEditingController = TextEditingController(); 35 | 36 | @override 37 | void updateValue(String? newValue) { 38 | super.updateValue(newValue); 39 | 40 | if (!_focusNode.hasFocus) { 41 | _textEditingController.text = newValue ?? ''; 42 | } 43 | } 44 | 45 | void _onTextChanged(String newValue) { 46 | updateValue(newValue); 47 | } 48 | 49 | @override 50 | Widget builder( 51 | BuildContext context, 52 | String? currentValue, 53 | void Function(String) updateValue, 54 | ) { 55 | return Row( 56 | key: ValueKey(stateKey), 57 | children: [ 58 | SizedBox( 59 | width: EnvironmentControlPanel.labelWidth, 60 | child: Text(title), 61 | ), 62 | Expanded( 63 | child: TextField( 64 | controller: _textEditingController, 65 | focusNode: _focusNode, 66 | onChanged: _onTextChanged, 67 | ), 68 | ), 69 | ], 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/environment/environment_control_panel.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | /// Contains a set of controls used to change environment parameters (text 20 | /// scale, viewport size, dark/light mode, etc.) along with any widgets provided 21 | /// by the current Scene's [environmentControlBuilders]. 22 | class EnvironmentControlPanel extends StatefulWidget { 23 | /// Creates an [EnvironmentControlPanel]. 24 | const EnvironmentControlPanel({ 25 | super.key, 26 | required this.children, 27 | this.targetPlatform, 28 | }); 29 | 30 | /// Standard width used for individual control title labels. 31 | static const double labelWidth = 80; 32 | 33 | /// Widgets displayed in the panel. 34 | final List children; 35 | 36 | /// Active [TargetPlatform]. Defaults to the current platform if null. 37 | final TargetPlatform? targetPlatform; 38 | 39 | @override 40 | State createState() => 41 | _EnvironmentControlPanelState(); 42 | } 43 | 44 | class _EnvironmentControlPanelState extends State { 45 | @override 46 | Widget build(BuildContext context) { 47 | return Material( 48 | elevation: 4, 49 | borderRadius: const BorderRadius.only( 50 | topRight: Radius.circular(4), 51 | bottomRight: Radius.circular(4), 52 | ), 53 | child: SingleChildScrollView( 54 | child: Padding( 55 | padding: const EdgeInsets.all(10), 56 | child: Column( 57 | crossAxisAlignment: CrossAxisAlignment.start, 58 | children: [ 59 | ...widget.children, 60 | ], 61 | ), 62 | ), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/environment/state/environment_state.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/widgets.dart'; 18 | 19 | /// An observable key-value store containing values that control how Scenes are 20 | /// presented, such as whether dark mode is enabled, whether bold text is 21 | /// enabled, etc. 22 | class EnvironmentState extends ChangeNotifier { 23 | /// Creates a new instance of [EnvironmentState] for testing purposes. 24 | factory EnvironmentState.forTest() => EnvironmentState._internal(); 25 | 26 | EnvironmentState._internal(); 27 | 28 | /// The [EnvironmentState] singleton. 29 | static EnvironmentState get instance => _instance; 30 | 31 | static final EnvironmentState _instance = EnvironmentState._internal(); 32 | 33 | /// The number of font pixels for each logical pixel. 34 | static const String textScaleKey = 'EnvironmentState.textScale'; 35 | 36 | /// Whether to use [Brightness.dark]. 37 | static const String isDarkModeKey = 'EnvironmentState.isDarkMode'; 38 | 39 | /// Whether text is drawn with a bold font weight. 40 | static const String isTextBoldKey = 'EnvironmentState.isTextBold'; 41 | 42 | /// Whether a [SemanticsDebugger] is shown over the UI. 43 | /// 44 | /// NOTE: this may not behave as expected until 45 | /// https://github.com/flutter/flutter/issues/120615 is fixed. 46 | static const String showSemanticsKey = 'EnvironmentState.showSemantics'; 47 | 48 | /// The platform that user interaction should adapt to target. 49 | /// 50 | /// If null, defaults to the current platform. 51 | static const String targetPlatformKey = 'EnvironmentState.targetPlatform'; 52 | 53 | /// A custom vertical size value. 54 | /// 55 | /// If null, the current window/screen height will be used. 56 | static const String heightOverrideKey = 'EnvironmentState.heightOverride'; 57 | 58 | /// A custom horizontal size value. 59 | /// 60 | /// If null, the current window/screen height will be used. 61 | static const String widthOverrideKey = 'EnvironmentState.widthOverride'; 62 | 63 | /// A screen size preset 64 | /// 65 | /// If null, the current window/screen size will be used. 66 | static const String devicePreviewKey = 'EnvironmentState.devicePreviewKey'; 67 | 68 | /// Stores current named environment state values. 69 | Map _stateMap = {}; 70 | 71 | /// Stores default named environment state values. 72 | final Map _stateMapDefaults = {}; 73 | 74 | /// Sets a default [value] for [key]. 75 | /// 76 | /// Calling [EnvironmentState.reset] will set the current value for [key] to 77 | /// [value]. If a default has already been set for [key], it will be 78 | /// overwritten. 79 | void setDefault(String key, Object? value) { 80 | _stateMapDefaults[key] = value; 81 | 82 | if (!_stateMap.containsKey(key)) { 83 | _stateMap[key] = value; 84 | } 85 | } 86 | 87 | /// Returns the value corresponding to [key] if it exists. 88 | T? get(String key) => _stateMap[key]; 89 | 90 | /// Updates the environment value corresponding to [key]. 91 | /// 92 | /// If no default has been set for this [key], [value] will be used as the 93 | /// default. 94 | void set(String key, dynamic value) { 95 | // If this key is new, use [value] as the default. 96 | if (!_stateMap.containsKey(key)) { 97 | _stateMapDefaults[key] = value; 98 | } 99 | 100 | _stateMap[key] = value; 101 | notifyListeners(); 102 | } 103 | 104 | /// Resets all environment variables to their default state and notifies 105 | /// listeners. 106 | void reset() { 107 | _stateMap = Map.from(_stateMapDefaults); 108 | notifyListeners(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/src/environment/state/screen_size_preset.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | /// A named screen size, usually a popular device and its viewport dimensions. 20 | class ScreenSizePreset { 21 | /// A named screen size, usually a popular device and its viewport dimensions. 22 | const ScreenSizePreset({required this.name, required this.size}); 23 | 24 | /// The device associated with [size]. 25 | final String name; 26 | 27 | /// The logical height and width of the device. 28 | final Size size; 29 | 30 | static const ScreenSizePreset _galaxyA51_71 = ScreenSizePreset( 31 | name: 'Galaxy A51/71', 32 | size: Size(412, 914), 33 | ); 34 | 35 | static const ScreenSizePreset _galaxyFold = ScreenSizePreset( 36 | name: 'Galaxy Fold', 37 | size: Size(280, 653), 38 | ); 39 | 40 | static const ScreenSizePreset _galaxyS20Ultra = ScreenSizePreset( 41 | name: 'Galaxy S20 Ultra', 42 | size: Size(412, 915), 43 | ); 44 | 45 | static const ScreenSizePreset _galaxyS8Plus = ScreenSizePreset( 46 | name: 'Galaxy S8+', 47 | size: Size(360, 740), 48 | ); 49 | 50 | static const ScreenSizePreset _iPadAir = ScreenSizePreset( 51 | name: 'iPad Air', 52 | size: Size(820, 1180), 53 | ); 54 | 55 | static const ScreenSizePreset _iPadMini = ScreenSizePreset( 56 | name: 'iPad Mini', 57 | size: Size(768, 1024), 58 | ); 59 | 60 | static const ScreenSizePreset _iPadPro11 = ScreenSizePreset( 61 | name: 'iPad Pro 11"', 62 | size: Size(834, 1194), 63 | ); 64 | 65 | static const ScreenSizePreset _iPadPro12_9 = ScreenSizePreset( 66 | name: 'iPad Pro 12.9"', 67 | size: Size(1024, 1366), 68 | ); 69 | 70 | static const ScreenSizePreset _iPhone12Pro = ScreenSizePreset( 71 | name: 'iPhone 12 Pro', 72 | size: Size(390, 844), 73 | ); 74 | 75 | static const ScreenSizePreset _iPhone14Plus = ScreenSizePreset( 76 | name: 'iPhone 14 Plus', 77 | size: Size(429, 926), 78 | ); 79 | 80 | static const ScreenSizePreset _iPhoneSE = ScreenSizePreset( 81 | name: 'iPhone SE', 82 | size: Size(375, 667), 83 | ); 84 | 85 | static const ScreenSizePreset _pixel5 = ScreenSizePreset( 86 | name: 'Pixel 5', 87 | size: Size(393, 851), 88 | ); 89 | 90 | static const ScreenSizePreset _pixel6Pro = ScreenSizePreset( 91 | name: 'Pixel 6 Pro', 92 | size: Size(360, 780), 93 | ); 94 | 95 | static const ScreenSizePreset _surfacePro7 = ScreenSizePreset( 96 | name: 'Surface Pro 7', 97 | size: Size(912, 1368), 98 | ); 99 | 100 | /// All available device size presets. 101 | static const List all = [ 102 | _galaxyA51_71, 103 | _galaxyFold, 104 | _galaxyS20Ultra, 105 | _galaxyS8Plus, 106 | _iPadAir, 107 | _iPadMini, 108 | _iPadPro11, 109 | _iPadPro12_9, 110 | _iPhone12Pro, 111 | _iPhone14Plus, 112 | _iPhoneSE, 113 | _pixel5, 114 | _pixel6Pro, 115 | _surfacePro7, 116 | ]; 117 | 118 | @override 119 | String toString() { 120 | return '$name (${size.width.toInt()} x ${size.height.toInt()})'; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/src/environment_aware_app.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | /// A [MaterialApp] that inherits its [MediaQuery] and applies the 20 | /// [platform] from its ancestors to default Material dark and light themes. 21 | /// 22 | /// This class makes it easier to write Scenes that interact well with the 23 | /// environment manipulation feature provided by [StagerApp]. This class is not 24 | /// required to use Stager, but you will need to ensure that you are properly 25 | /// consuming the [MediaQuery] and [ThemeData.platform] for the environment 26 | /// manipulation feature to work properly. 27 | class EnvironmentAwareApp extends StatelessWidget { 28 | /// Creates an [EnvironmentAwareApp] with the provided [home]. 29 | const EnvironmentAwareApp({ 30 | super.key, 31 | required this.home, 32 | this.localizationsDelegates, 33 | }); 34 | 35 | /// The widget wrapped by this app. 36 | final Widget home; 37 | 38 | /// Optional list of [LocalizationsDelegate]s, forwarded to the wrapped 39 | /// [MaterialApp]. 40 | final Iterable>? localizationsDelegates; 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return MaterialApp( 45 | useInheritedMediaQuery: true, 46 | theme: ThemeData.light().copyWith(platform: Theme.of(context).platform), 47 | darkTheme: 48 | ThemeData.dark().copyWith(platform: Theme.of(context).platform), 49 | home: home, 50 | localizationsDelegates: localizationsDelegates, 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/extensions/string_extensions.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /// Adds [isNullOrEmpty] property to [String?] 18 | extension NullOrEmpty on String? { 19 | /// Whether this string is null or empty. 20 | bool get isNullOrEmpty => this == null || this!.isEmpty; 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/scene_list.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import 'environment/state/environment_state.dart'; 20 | import 'scene.dart'; 21 | import 'scene_container.dart'; 22 | 23 | /// A [ListView] showing available [Scenes]. Tapping on a Scene name will setUp 24 | /// the Scene before displaying it. 25 | class SceneList extends StatefulWidget { 26 | /// Creates a [SceneList] widget. 27 | const SceneList({ 28 | super.key, 29 | required this.scenes, 30 | required this.environmentState, 31 | }); 32 | 33 | /// The list of [Scene]s displayed by this widget. 34 | final List scenes; 35 | 36 | /// The shared state backing [scenes]. 37 | final EnvironmentState environmentState; 38 | 39 | @override 40 | State createState() => _SceneListState(); 41 | } 42 | 43 | class _SceneListState extends State { 44 | EnvironmentState get environmentState => widget.environmentState; 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return Scaffold( 49 | appBar: AppBar(title: const Text('Scenes')), 50 | body: ListView.separated( 51 | itemBuilder: (BuildContext context, int index) { 52 | final Scene scene = widget.scenes[index]; 53 | return ListTile( 54 | title: Text(widget.scenes[index].title), 55 | onTap: () async { 56 | await scene.setUp(); 57 | if (!mounted) { 58 | return; 59 | } 60 | 61 | Navigator.of(context).push( 62 | MaterialPageRoute( 63 | builder: (_) => SceneContainer( 64 | scene: scene, 65 | environmentState: environmentState, 66 | ), 67 | ), 68 | ); 69 | }, 70 | ); 71 | }, 72 | separatorBuilder: (_, __) => const Divider(), 73 | itemCount: widget.scenes.length, 74 | ), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/src/stager_app.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import 'environment/state/environment_state.dart'; 20 | import 'scene.dart'; 21 | import 'scene_container.dart'; 22 | import 'scene_list.dart'; 23 | 24 | /// A convenience method to run [scenes] in a [StagerApp]. 25 | void runStagerApp({required List scenes}) => 26 | runApp(StagerApp(scenes: scenes)); 27 | 28 | /// A simple app that displays a list of [Scene]s and navigates to them on tap. 29 | /// 30 | /// If only one Scene is provided, that Scene will be shown as though it had 31 | /// been navigated to from a list of Scenes. 32 | class StagerApp extends StatefulWidget { 33 | /// Creates a [StagerApp] with the given [scenes], which share 34 | /// [environmentState]. If no value is provided for [environmentState], the 35 | /// default [EnvironmentState.instance] will be used. 36 | StagerApp({ 37 | super.key, 38 | required this.scenes, 39 | EnvironmentState? environmentState, 40 | }) : environmentState = environmentState ?? EnvironmentState.instance; 41 | 42 | /// The [Scene]s being displayed by this app. 43 | final List scenes; 44 | 45 | /// An observable data store containing values that control how Scenes are 46 | /// presented, such as whether dark mode is enabled, whether bold text is 47 | /// enabled, etc. 48 | final EnvironmentState environmentState; 49 | 50 | @override 51 | State createState() => _StagerAppState(); 52 | } 53 | 54 | class _StagerAppState extends State { 55 | late Future _sceneSetUpFuture; 56 | 57 | bool get isSingleScene => widget.scenes.length == 1; 58 | 59 | EnvironmentState get environmentState => widget.environmentState; 60 | 61 | @override 62 | void initState() { 63 | super.initState(); 64 | if (isSingleScene) { 65 | _sceneSetUpFuture = widget.scenes.first.setUp(); 66 | } else { 67 | _sceneSetUpFuture = Future.value(); 68 | } 69 | } 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | return FutureBuilder( 74 | future: _sceneSetUpFuture, 75 | builder: (BuildContext context, AsyncSnapshot snapshot) { 76 | if (snapshot.connectionState == ConnectionState.waiting) { 77 | return const Center( 78 | child: CircularProgressIndicator(), 79 | ); 80 | } 81 | 82 | return AnimatedBuilder( 83 | animation: environmentState, 84 | builder: (BuildContext context, _) => MaterialApp( 85 | home: isSingleScene 86 | ? SceneContainer( 87 | scene: widget.scenes.first, 88 | environmentState: environmentState, 89 | ) 90 | : SceneList( 91 | scenes: widget.scenes, 92 | environmentState: environmentState, 93 | ), 94 | ), 95 | ); 96 | }, 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/src/stager_app_generator.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:analyzer/dart/element/element.dart'; 18 | import 'package:analyzer/dart/element/type.dart'; 19 | import 'package:build/build.dart'; 20 | import 'package:source_gen/source_gen.dart'; 21 | 22 | /// Generates a dart file containing a main function that launches a [StagerApp] 23 | /// which lists all titled [Scene] subclasses. 24 | class StagerAppGenerator extends Generator { 25 | @override 26 | Future generate( 27 | LibraryReader library, 28 | BuildStep buildStep, 29 | ) async { 30 | // We only want to display scenes with titles. We assume [Scene] 31 | // subclasses without titles are base classes. 32 | final Iterable sceneElements = library.allElements 33 | .whereType() 34 | .where( 35 | (ClassElement classElement) => classElement.allSupertypes.any( 36 | (InterfaceType supertype) => supertype.element.name == 'Scene', 37 | ), 38 | ) 39 | .where( 40 | (ClassElement classElement) => classElement.accessors.any( 41 | (PropertyAccessorElement accessor) => 42 | accessor.hasOverride && accessor.name == 'title', 43 | ), 44 | ); 45 | if (sceneElements.isEmpty) { 46 | return null; 47 | } 48 | 49 | final StringBuffer buffer = StringBuffer(); 50 | 51 | final String importsString = sceneElements 52 | .map((ClassElement e) => e.source.shortName) 53 | .toSet() 54 | .map((String shortName) => "import '$shortName';") 55 | .join('\n'); 56 | 57 | final String sceneConstructorsString = 58 | sceneElements.map((ClassElement e) => '${e.name}(),').join('\n'); 59 | 60 | buffer.write(''' 61 | 62 | import 'package:stager/stager.dart'; 63 | 64 | $importsString 65 | 66 | void main() { 67 | final List scenes = [ 68 | $sceneConstructorsString 69 | ]; 70 | 71 | if (const String.fromEnvironment('Scene').isNotEmpty) { 72 | const String sceneName = String.fromEnvironment('Scene'); 73 | final Scene scene = 74 | scenes.firstWhere((Scene scene) => scene.title == sceneName); 75 | runStagerApp(scenes: [scene]); 76 | } else { 77 | runStagerApp(scenes: scenes); 78 | } 79 | } 80 | '''); 81 | 82 | return buffer.toString(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/stager.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | export 'src/environment/controls/boolean_control.dart'; 18 | export 'src/environment/controls/dropdown_control.dart'; 19 | export 'src/environment/controls/environment_control.dart'; 20 | export 'src/environment/controls/stepper_control.dart'; 21 | export 'src/environment/controls/text_input_control.dart'; 22 | export 'src/environment/state/environment_state.dart'; 23 | export 'src/environment/state/screen_size_preset.dart'; 24 | export 'src/environment_aware_app.dart'; 25 | export 'src/scene.dart'; 26 | export 'src/stager_app.dart'; 27 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | name: stager 16 | description: A Flutter productivity tool that allows developers to isolate pieces of UI. 17 | version: 1.0.0 18 | repository: https://github.com/bryanoltman/stager 19 | issue_tracker: https://github.com/bryanoltman/stager/issues 20 | 21 | environment: 22 | sdk: ">=2.17.0 <4.0.0" 23 | flutter: ">=1.16.0" 24 | 25 | dependencies: 26 | analyzer: ">=5.1.0 <5.6.0" 27 | build: ^2.3.1 28 | flutter: 29 | sdk: flutter 30 | source_gen: ^1.2.7 31 | 32 | dev_dependencies: 33 | flutter_test: 34 | sdk: flutter 35 | lints: ^2.0.1 36 | -------------------------------------------------------------------------------- /test/scene_container_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | import 'package:flutter_test/flutter_test.dart'; 19 | import 'package:stager/stager.dart'; 20 | 21 | void main() { 22 | testWidgets('collapses EnvironmentControlPanel on narrow screens', 23 | (WidgetTester tester) async { 24 | tester.binding.window.physicalSizeTestValue = const Size(500, 600); 25 | tester.binding.window.devicePixelRatioTestValue = 1.0; 26 | 27 | final StagerApp stagerApp = StagerApp(scenes: [TextScene()]); 28 | await tester.pumpWidget(stagerApp); 29 | await tester.pumpAndSettle(); 30 | 31 | expect( 32 | find.byKey(const ValueKey('ToggleEnvironmentControlPanelButton')), 33 | findsOneWidget, 34 | ); 35 | }); 36 | 37 | testWidgets('expands EnvironmentControlPanel on larger screens', 38 | (WidgetTester tester) async { 39 | tester.binding.window.physicalSizeTestValue = const Size(2160, 3840); 40 | tester.binding.window.devicePixelRatioTestValue = 1.0; 41 | 42 | final StagerApp stagerApp = StagerApp(scenes: [TextScene()]); 43 | await tester.pumpWidget(stagerApp); 44 | await tester.pumpAndSettle(); 45 | 46 | expect( 47 | find.byKey(const ValueKey('ToggleEnvironmentControlPanelButton')), 48 | findsNothing, 49 | ); 50 | }); 51 | } 52 | 53 | class TextScene extends Scene { 54 | @override 55 | String get title => 'Text'; 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | return const Text('Text Scene'); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/stager_app_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | import 'package:flutter_test/flutter_test.dart'; 19 | import 'package:stager/src/scene_container.dart'; 20 | import 'package:stager/stager.dart'; 21 | 22 | EnvironmentState environmentState = EnvironmentState.forTest(); 23 | 24 | void main() { 25 | final List scenes = [ 26 | ButtonScene(), 27 | TextScene(), 28 | ]; 29 | 30 | setUp(() { 31 | environmentState.reset(); 32 | }); 33 | 34 | testWidgets('displays a list of Scenes', (WidgetTester tester) async { 35 | final StagerApp stagerApp = StagerApp(scenes: scenes); 36 | await tester.pumpWidget(stagerApp); 37 | await tester.pumpAndSettle(); 38 | expect(find.text('Text'), findsOneWidget); 39 | expect(find.text('Button'), findsOneWidget); 40 | }); 41 | 42 | testWidgets('displays a back button after navigating to a Scene', 43 | (WidgetTester tester) async { 44 | final StagerApp stagerApp = StagerApp(scenes: scenes); 45 | await tester.pumpWidget(stagerApp); 46 | await tester.pumpAndSettle(); 47 | 48 | // Tap the "Text" row to push TextScene onto the navigation stack. 49 | await tester.tap(find.text('Text')); 50 | await tester.pumpAndSettle(); 51 | 52 | // Verify that our SceneContainer is present. 53 | expect(find.byType(SceneContainer), findsOneWidget); 54 | 55 | // Verify that tapping the back button navigates back. 56 | await tester.tap(find.byKey( 57 | const ValueKey('PopToSceneListButton'), 58 | )); 59 | await tester.pumpAndSettle(); 60 | expect(find.byType(SceneContainer), findsNothing); 61 | expect(find.text('Text'), findsOneWidget); 62 | expect(find.text('Button'), findsOneWidget); 63 | }); 64 | 65 | testWidgets('environment state persists between scenes', 66 | (WidgetTester tester) async { 67 | final StagerApp stagerApp = StagerApp( 68 | scenes: scenes, 69 | environmentState: environmentState, 70 | ); 71 | await tester.pumpWidget(stagerApp); 72 | await tester.pumpAndSettle(); 73 | 74 | // Tap the "Text" row to push TextScene onto the navigation stack. 75 | await tester.tap(find.text('Text')); 76 | await tester.pumpAndSettle(); 77 | 78 | await tester.enterText( 79 | find.descendant( 80 | of: find.byKey( 81 | const ValueKey('state'), 82 | ), 83 | matching: find.byType(TextField), 84 | ), 85 | 'testing testing testing', 86 | ); 87 | await tester.pumpAndSettle(); 88 | 89 | // Navigate back to the Scene list. 90 | await tester.tap(find.byKey( 91 | const ValueKey('PopToSceneListButton'), 92 | )); 93 | await tester.pumpAndSettle(); 94 | 95 | // Navigate to the button scene. 96 | await tester.tap(find.text('Button')); 97 | await tester.pumpAndSettle(); 98 | 99 | expect( 100 | find.text('testing testing testing'), 101 | findsOneWidget, 102 | ); 103 | }); 104 | } 105 | 106 | final TextInputControl textInputControl = TextInputControl( 107 | title: 'Custom control', 108 | stateKey: 'state', 109 | defaultValue: '', 110 | environmentState: environmentState, 111 | ); 112 | 113 | class TextScene extends Scene { 114 | @override 115 | final List> environmentControls = 116 | >[ 117 | textInputControl, 118 | ]; 119 | 120 | @override 121 | String get title => 'Text'; 122 | 123 | @override 124 | Widget build(BuildContext context) => const Text('Text Scene'); 125 | } 126 | 127 | class ButtonScene extends Scene { 128 | @override 129 | final List> environmentControls = 130 | >[ 131 | textInputControl, 132 | ]; 133 | 134 | @override 135 | String get title => 'Button'; 136 | 137 | @override 138 | Widget build(BuildContext context) => ElevatedButton( 139 | onPressed: () {}, 140 | child: const Text('My Button'), 141 | ); 142 | } 143 | --------------------------------------------------------------------------------