├── .circleci └── config.yml ├── .github └── workflows │ └── review.yaml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── analysis_options.yaml ├── examples ├── generator_app │ ├── README.md │ ├── android │ │ ├── .gitignore │ │ ├── app │ │ │ ├── build.gradle │ │ │ └── src │ │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── generator_app │ │ │ │ │ │ └── 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 │ ├── assets │ │ ├── flutter.png │ │ └── fonts │ │ │ └── Roboto-Regular.ttf │ ├── build.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 │ │ └── RunnerTests │ │ │ └── RunnerTests.swift │ ├── lib │ │ ├── bar │ │ │ ├── bar.dart │ │ │ └── bar.story.dart │ │ ├── foo │ │ │ └── foo_widget.story.dart │ │ ├── generated_playbook.dart │ │ ├── image │ │ │ └── asset_image.story.dart │ │ ├── main.dart │ │ ├── main_catalog.dart │ │ ├── page │ │ │ ├── page.dart │ │ │ └── page.story.dart │ │ └── scrollable │ │ │ ├── scrollable.dart │ │ │ └── scrollable.story.dart │ ├── pubspec.yaml │ ├── pubspec_overrides.yaml │ └── test │ │ └── snapshot_test.dart └── simple_app │ ├── README.md │ ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── simple_app │ │ │ │ │ └── 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 │ ├── assets │ ├── flutter.png │ └── fonts │ │ └── Roboto-Regular.ttf │ ├── 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 │ └── RunnerTests │ │ └── RunnerTests.swift │ ├── lib │ ├── bar │ │ ├── bar.dart │ │ └── bar.story.dart │ ├── foo │ │ └── foo_widget.story.dart │ ├── image │ │ └── asset_image.story.dart │ ├── main.dart │ ├── main_catalog.dart │ ├── page │ │ ├── page.dart │ │ └── page.story.dart │ └── scrollable │ │ ├── scrollable.dart │ │ └── scrollable.story.dart │ ├── pubspec.yaml │ ├── pubspec_overrides.yaml │ └── test │ └── snapshot_test.dart ├── melos.yaml ├── package.json ├── packages ├── playbook │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── lib │ │ ├── playbook.dart │ │ ├── playbook_annotations.dart │ │ └── src │ │ │ ├── generate_scenario.dart │ │ │ ├── playbook.dart │ │ │ ├── scenario.dart │ │ │ ├── scenario_layout.dart │ │ │ ├── scenario_widget.dart │ │ │ └── story.dart │ ├── pubspec.yaml │ └── test │ │ └── playbook_test.dart ├── playbook_generator │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── build.yaml │ ├── lib │ │ ├── playbook_generator.dart │ │ └── src │ │ │ ├── constant_reader_utils.dart │ │ │ └── playbook_builder.dart │ └── pubspec.yaml ├── playbook_snapshot │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── lib │ │ ├── playbook_snapshot.dart │ │ └── src │ │ │ ├── font_builder.dart │ │ │ ├── pubspec_reader.dart │ │ │ ├── snapshot.dart │ │ │ ├── snapshot_device.dart │ │ │ ├── snapshot_support.dart │ │ │ └── test_tool.dart │ ├── pubspec.yaml │ └── test │ │ └── playbook_snapshot_test.dart └── playbook_ui │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── lib │ ├── playbook_ui.dart │ └── src │ │ ├── component │ │ ├── component.dart │ │ ├── dialog_scaffold.dart │ │ ├── scalable_button.dart │ │ ├── search_box.dart │ │ └── story_drawer.dart │ │ ├── playbook_gallery.dart │ │ └── scenario_container.dart │ ├── pubspec.yaml │ └── test │ └── playbook_ui_test.dart ├── pubspec.yaml ├── regconfig.json ├── renovate.json └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | flutter: circleci/flutter@2.1.0 5 | node: circleci/node@7.1.0 6 | 7 | executors: 8 | android_medium: 9 | docker: 10 | - image: circleci/android:api-30 11 | resource_class: medium 12 | working_directory: ~/workspace 13 | 14 | jobs: 15 | vrt: 16 | executor: android_medium 17 | steps: 18 | - checkout 19 | - node/install: 20 | install-yarn: true 21 | - flutter/install_sdk: 22 | version: "2.5.1" 23 | - run: 24 | name: Set environment paths 25 | command: | 26 | echo "export PATH=$PATH:$HOME/.pub-cache/bin" >> $BASH_ENV 27 | source $BASH_ENV 28 | - run: 29 | name: Run melos 30 | command: | 31 | dart pub global activate melos 32 | melos run pub:get 33 | - run: 34 | name: Run Golden Testing for CatalogApp 35 | command: melos run test:snapshot:simple 36 | - restore_cache: 37 | name: Restoring yarn cache 38 | key: v0-yarn-{{ checksum "yarn.lock" }} 39 | - run: 40 | name: Install dependencies 41 | command: | 42 | yarn --frozen-lockfile 43 | - save_cache: 44 | name: Saving yarn cache 45 | key: v0-yarn-{{ checksum "yarn.lock" }} 46 | paths: 47 | - node_modules 48 | - run: 49 | name: Run VRT 50 | command: | 51 | export GOOGLE_APPLICATION_CREDENTIALS=gcloud-service-key.json 52 | echo $GCLOUD_SERVICE_KEY > $GOOGLE_APPLICATION_CREDENTIALS 53 | yarn regression 54 | 55 | workflows: 56 | vrt: 57 | jobs: 58 | - vrt 59 | -------------------------------------------------------------------------------- /.github/workflows/review.yaml: -------------------------------------------------------------------------------- 1 | name: Review 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Flutter 18 | uses: subosito/flutter-action@v2 19 | with: 20 | flutter-version: 3.27.4 21 | cache: true 22 | 23 | - name: Setup pub 24 | run: make setup/pub 25 | 26 | - name: Dart Analyzer 27 | uses: invertase/github-action-dart-analyzer@v3 28 | with: 29 | fatal-infos: true 30 | working-directory: ./packages 31 | 32 | - name: Test 33 | run: melos test 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/guides/libraries/private-files 2 | 3 | # Miscellaneous 4 | *.class 5 | *.log 6 | *.pyc 7 | *.swp 8 | .DS_Store 9 | .atom/ 10 | .buildlog/ 11 | .history 12 | .svn/ 13 | 14 | # IntelliJ related 15 | *.iml 16 | *.ipr 17 | *.iws 18 | .idea/ 19 | 20 | # The .vscode folder contains launch configuration and tasks you configure in 21 | # VS Code which you may wish to be included in version control, so this line 22 | # is commented out by default. 23 | #.vscode/ 24 | 25 | # Flutter/Dart/Pub related 26 | **/doc/api/ 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | build/ 34 | # Flutter/Dart/Pub related 35 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 36 | pubspec.lock 37 | pubspec_overrides.yaml 38 | !examples/**/pubspec_overrides.yaml 39 | 40 | # Android related 41 | **/android/**/gradle-wrapper.jar 42 | **/android/.gradle 43 | **/android/captures/ 44 | **/android/gradlew 45 | **/android/gradlew.bat 46 | **/android/local.properties 47 | **/android/**/GeneratedPluginRegistrant.java 48 | 49 | # iOS/XCode related 50 | **/ios/**/*.mode1v3 51 | **/ios/**/*.mode2v3 52 | **/ios/**/*.moved-aside 53 | **/ios/**/*.pbxuser 54 | **/ios/**/*.perspectivev3 55 | **/ios/**/*sync/ 56 | **/ios/**/.sconsign.dblite 57 | **/ios/**/.tags* 58 | **/ios/**/.vagrant/ 59 | **/ios/**/DerivedData/ 60 | **/ios/**/Icon? 61 | **/ios/**/Pods/ 62 | **/ios/**/.symlinks/ 63 | **/ios/**/profile 64 | **/ios/**/xcuserdata 65 | **/ios/.generated/ 66 | **/ios/Flutter/App.framework 67 | **/ios/Flutter/Flutter.framework 68 | **/ios/Flutter/Flutter.podspec 69 | **/ios/Flutter/Generated.xcconfig 70 | **/ios/Flutter/app.flx 71 | **/ios/Flutter/app.zip 72 | **/ios/Flutter/flutter_assets/ 73 | **/ios/Flutter/flutter_export_environment.sh 74 | **/ios/ServiceDefinitions.json 75 | **/ios/Runner/GeneratedPluginRegistrant.* 76 | 77 | # Exceptions to above rules. 78 | !**/ios/**/default.mode1v3 79 | !**/ios/**/default.mode2v3 80 | !**/ios/**/default.pbxuser 81 | !**/ios/**/default.perspectivev3 82 | 83 | # Playbook 84 | examples/**/test/screenshots/ 85 | node_modules/ 86 | .reg 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, playbook-ui 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: gen 2 | gen: 3 | melos gen 4 | 5 | .PHONY: setup/pub 6 | setup/pub: 7 | dart pub global activate melos 8 | dart pub get 9 | melos bootstrap 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Playbook 3 |

4 | 5 |

A library for isolated developing UI components and automatically taking snapshots of them.

6 | 7 | # Playbook 8 | 9 | `Playbook` is a library that provides a sandbox for building UI components without having to worry about application-specific dependencies, strongly inspired by [Storybook](https://storybook.js.org/) for JavaScript in web-frontend development. 10 | 11 | Components built by using `Playbook` can generate a standalone app as living styleguide. 12 | This allows you to not only review UI quickly but also deliver more robost designs by separating business logics out of components. 13 | 14 | Besides, snapshots of each component can be automatically generated by unit tests, and visual regression testing can be performed using arbitrary third-party tools. 15 | 16 | For complex modern app development, it’s important to catch UI changes more sensitively and keep improving them faster. 17 | With the `Playbook`, you don't have to struggle through preparing the data and spend human resources for manual testings. 18 | 19 | --- 20 | 21 | ### Playbook 22 | 23 | `Playbook` components are uniquely stored as scenarios. A `Scenario` has the way to layout component. 24 | 25 | ```dart 26 | Playbook( 27 | stories: [ 28 | Story( 29 | 'Home', 30 | scenarios: [ 31 | Scenario( 32 | 'CategoryHome', 33 | layout: ScenarioLayout.fill(), 34 | child: CategoryHome(userData: UserData.stub), 35 | ), 36 | Scenario( 37 | 'LandmarkList', 38 | layout: ScenarioLayout.fill(), 39 | child: Scaffold( 40 | appBar: AppBar(), 41 | body: LandmarkList(userData: UserData.stub), 42 | ), 43 | ), 44 | Scenario( 45 | 'Container red', 46 | layout: ScenarioLayout.fixed(100, 100), 47 | child: Container(color: Colors.red), 48 | ), 49 | ], 50 | ), 51 | ], 52 | ); 53 | ``` 54 | 55 | --- 56 | 57 | ### PlaybookUI 58 | 59 | `PlaybookUI` provides user interfaces for browsing a list of scenarios. 60 | 61 | #### PlaybookGallery 62 | 63 | The component visuals are listed and displayed. 64 | Those that are displayed on the top screen are not actually doing layout, but rather display the snapshots that are efficiently generated at runtime. 65 | 66 | | Browser | Detail | 67 | | ------- | ------ | 68 | ||| 69 | 70 | --- 71 | 72 | ### PlaybookSnapshot 73 | 74 | Scenarios can be tested by the instance of types conform to `TestTool` class. 75 | `Snapshot` is one of them, which can generate the snapshots of all scenarios with simulate the screen size and safe area of the given devices. 76 | 77 | ```dart 78 | Future main() async { 79 | testWidgets('Take snapshots', (tester) async { 80 | await Playbook( 81 | stories: [ 82 | barStory(), 83 | fooWidgetStory(), 84 | assetImageStory(), 85 | homePageStory(), 86 | scrollableStory(), 87 | ], 88 | ).run( 89 | Snapshot( 90 | devices: [SnapshotDevice.iPhoneSE2nd], 91 | ), 92 | (widget, device) { 93 | return MaterialApp( 94 | debugShowCheckedModeBanner: false, 95 | theme: ThemeData( 96 | fontFamily: 'Roboto', 97 | platform: device.platform, 98 | ), 99 | home: widget, 100 | ); 101 | }, 102 | ); 103 | }); 104 | } 105 | ``` 106 | 107 | generate images 108 | 109 | #### Configuration 110 | 111 | `pubspec.yaml` can be configured to define the location of the font file and snapshot output directory path. 112 | 113 | ```yaml 114 | playbook_snapshot: 115 | fonts: 116 | - family: Roboto 117 | fonts: 118 | - asset: assets/fonts/Roboto-Regular.ttf 119 | # default is /snapshots 120 | snapshot_dir: iOS 121 | # default is empty 122 | sub_dir: service 123 | ``` 124 | 125 | With the above settings, snapshots will be saved in the following directory. 126 | 127 | ``` 128 | /test/iOS/${DeviceName}/service/${StoryTitle}/${ScenarioTitle}.png 129 | ``` 130 | 131 | If you want to change path dynamically, you can use `snapshotDir` and `subDir` arguments in the `Snapshot`. 132 | 133 | #### Notes 134 | 135 | `Snapshot` (internally `flutter test --update-goldens`) requires you to prepare and load the fonts yourself. By defining the location of the font file in `flutter` or `playbook_snapshot` in `pubspec.yaml` and preparing the font file in the directory, the font file will be loaded automatically. 136 | 137 | ```yaml 138 | flutter: 139 | fonts: 140 | - family: Roboto 141 | fonts: 142 | - asset: assets/fonts/Roboto-Regular.ttf 143 | ``` 144 | 145 | or 146 | 147 | ```yaml 148 | playbook_snapshot: 149 | fonts: 150 | - family: Roboto 151 | fonts: 152 | - asset: assets/fonts/Roboto-Regular.ttf 153 | ``` 154 | 155 | And then, should be prepared the font file in the directory. 156 | 157 | --- 158 | 159 | ### PlaybookGenerator 160 | 161 | Supports generating stories and scenarios from any `.dart` files. 162 | 163 | ```dart 164 | // some_story.dart 165 | 166 | const storyTitle = 'Home'; 167 | 168 | @GenerateScenario( 169 | layout: ScenarioLayout.fill(), 170 | ) 171 | Widget $CategoryHome() => CategoryHome(userData: UserData.stub); 172 | 173 | @GenerateScenario( 174 | layout: ScenarioLayout.fill(), 175 | ) 176 | Widget $LandmarkList() => Scaffold( 177 | appBar: AppBar(), 178 | body: LandmarkList(userData: UserData.stub), 179 | ); 180 | 181 | @GenerateScenario( 182 | title: 'Container red', 183 | layout: ScenarioLayout.fixed(100, 100), 184 | ) 185 | Widget containerRed() => Container(color: Colors.red); 186 | 187 | @GenerateScenario( 188 | title: 'Device pixel ratio', 189 | layout: ScenarioLayout.fixed(300, 300), 190 | ) 191 | Widget devicePixelRatio(BuildContext context) => 192 | Text('Device pixel ratio is ${MediaQuery.of(context).devicePixelRatio}'); 193 | ``` 194 | 195 | You can reference the playbook instance. 196 | 197 | ```dart 198 | void main() { 199 | runApp(MyApp()); 200 | } 201 | 202 | class MyApp extends StatelessWidget { 203 | @override 204 | Widget build(BuildContext context) { 205 | return MaterialApp( 206 | title: 'Playbook Demo', 207 | theme: ThemeData.light(), 208 | home: PlaybookGallery( 209 | title: 'Sample app', 210 | playbook: playbook, 211 | ), 212 | ); 213 | } 214 | } 215 | ``` 216 | 217 | Can set glob for find assets and output file name in `build.yaml`. Default input value is `lib/**.dart` and output value is `generated_playbook.dart`. 218 | 219 | ```yaml 220 | targets: 221 | $default: 222 | builders: 223 | playbook_generator:stories: 224 | options: 225 | input: lib/**.dart 226 | output: generated_playbook.dart 227 | ``` 228 | 229 | --- 230 | 231 | ### Integration with Third-party Tools 232 | 233 | The generated snapshot images can be used for more advanced visual regression testing by using a variety of third party tools. 234 | 235 | #### [percy](https://percy.io) 236 | 237 | percy 238 | 239 | #### [reg-viz/reg-suit](https://github.com/reg-viz/reg-suit) 240 | 241 | reg-suit 242 | 243 | --- 244 | 245 | ## Requirements 246 | 247 | - Dart 3.0.0+ 248 | - flutter 3.0.0+ 249 | 250 | --- 251 | 252 | ## License 253 | 254 | Playbook is released under the [BSD-3-Clause License](./LICENSE). 255 | 256 |
257 |

258 | Playbook 259 |

260 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:very_good_analysis/analysis_options.7.0.0.yaml 2 | 3 | analyzer: 4 | errors: 5 | document_ignores: ignore 6 | exclude: 7 | - '**/generated_playbook.dart' 8 | 9 | linter: 10 | rules: 11 | sort_pub_dependencies: false 12 | flutter_style_todos: false 13 | public_member_api_docs: false 14 | one_member_abstracts: false 15 | always_put_required_named_parameters_first: false 16 | avoid_catches_without_on_clauses: false 17 | -------------------------------------------------------------------------------- /examples/generator_app/README.md: -------------------------------------------------------------------------------- 1 | # sample app 2 | -------------------------------------------------------------------------------- /examples/generator_app/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 | -------------------------------------------------------------------------------- /examples/generator_app/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | namespace "com.example.generator_app" 27 | compileSdk flutter.compileSdkVersion 28 | ndkVersion flutter.ndkVersion 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 45 | applicationId "com.example.generator_app" 46 | // You can update the following values to match your application needs. 47 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 48 | minSdkVersion flutter.minSdkVersion 49 | targetSdkVersion flutter.targetSdkVersion 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | } 53 | 54 | buildTypes { 55 | release { 56 | // TODO: Add your own signing config for the release build. 57 | // Signing with the debug keys for now, so `flutter run --release` works. 58 | signingConfig signingConfigs.debug 59 | } 60 | } 61 | } 62 | 63 | flutter { 64 | source '../..' 65 | } 66 | 67 | dependencies {} 68 | -------------------------------------------------------------------------------- /examples/generator_app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/generator_app/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/generator_app/android/app/src/main/kotlin/com/example/generator_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.generator_app 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /examples/generator_app/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /examples/generator_app/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /examples/generator_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/generator_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/generator_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/generator_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/generator_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/generator_app/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /examples/generator_app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /examples/generator_app/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/generator_app/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /examples/generator_app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /examples/generator_app/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.6.3-all.zip 6 | -------------------------------------------------------------------------------- /examples/generator_app/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | } 19 | 20 | plugins { 21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 22 | id "com.android.application" version "7.3.0" apply false 23 | id "org.jetbrains.kotlin.android" version "1.7.10" apply false 24 | } 25 | 26 | include ":app" 27 | -------------------------------------------------------------------------------- /examples/generator_app/assets/flutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/assets/flutter.png -------------------------------------------------------------------------------- /examples/generator_app/assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /examples/generator_app/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | playbook_generator:stories: 5 | options: 6 | input: lib/**.story.dart 7 | output: generated_playbook.dart 8 | -------------------------------------------------------------------------------- /examples/generator_app/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 | -------------------------------------------------------------------------------- /examples/generator_app/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 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/generator_app/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 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /examples/generator_app/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 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/generator_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /examples/generator_app/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. -------------------------------------------------------------------------------- /examples/generator_app/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 | -------------------------------------------------------------------------------- /examples/generator_app/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 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Generator App 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | generator_app 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 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /examples/generator_app/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /examples/generator_app/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /examples/generator_app/lib/bar/bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BarWidget extends StatelessWidget { 4 | const BarWidget({ 5 | super.key, 6 | required this.text, 7 | }); 8 | 9 | final String text; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return ColoredBox( 14 | color: Colors.amberAccent, 15 | child: Row( 16 | mainAxisAlignment: MainAxisAlignment.center, 17 | children: [ 18 | const Icon(Icons.star), 19 | const SizedBox(width: 16), 20 | Text(text, style: Theme.of(context).textTheme.headlineSmall), 21 | ], 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/generator_app/lib/bar/bar.story.dart: -------------------------------------------------------------------------------- 1 | import 'package:generator_app/bar/bar.dart'; 2 | import 'package:playbook/playbook.dart'; 3 | 4 | const storyTitle = 'BarWidget'; 5 | 6 | Scenario twoLineText() { 7 | return const Scenario( 8 | 'TwoLine', 9 | child: BarWidget(text: 'TextTextText\nTextText'), 10 | ); 11 | } 12 | 13 | Scenario emptyText() { 14 | return const Scenario('Empty', child: BarWidget(text: '')); 15 | } 16 | 17 | List variousText() { 18 | return ['Text 1', 'Text2'] 19 | .map( 20 | (e) => Scenario(e, child: BarWidget(text: e)), 21 | ) 22 | .toList(); 23 | } 24 | -------------------------------------------------------------------------------- /examples/generator_app/lib/foo/foo_widget.story.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: camel_case_types 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:playbook/playbook.dart'; 5 | 6 | const storyTitle = 'FooWidget'; 7 | 8 | Scenario foo1() { 9 | return Scenario('Hello', child: Container(color: Colors.blue)); 10 | } 11 | 12 | Scenario foo2() { 13 | return Scenario( 14 | 'World', 15 | alignment: Alignment.topRight, 16 | child: Center( 17 | child: Container(width: 200, height: 250, color: Colors.amber), 18 | ), 19 | ); 20 | } 21 | 22 | @GenerateScenario( 23 | title: 'foo3foo3', 24 | layout: ScenarioLayout.sizing( 25 | ScenarioLayoutFixed(100), 26 | ScenarioLayoutFixed(200), 27 | ), 28 | ) 29 | class Foo3 extends StatelessWidget { 30 | const Foo3({super.key}); 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Container(color: Colors.amber); 35 | } 36 | } 37 | 38 | @GenerateScenario( 39 | layout: ScenarioLayout.fixedH( 40 | 300, 41 | crossAxisLayout: ScenarioLayoutFill(), 42 | ), 43 | ) 44 | class $Foo4_Bar extends StatelessWidget { 45 | const $Foo4_Bar({super.key}); 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | return Container(color: Colors.pink); 50 | } 51 | } 52 | 53 | @GenerateScenario( 54 | layout: ScenarioLayout.fixedV( 55 | 500, 56 | crossAxisLayout: ScenarioLayoutFixed(200), 57 | ), 58 | ) 59 | Widget $foo5() => const Text('Foo 5'); 60 | -------------------------------------------------------------------------------- /examples/generator_app/lib/generated_playbook.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ignore_for_file: no_leading_underscores_for_library_prefixes 4 | import 'package:generator_app/bar/bar.story.dart' as _i7; 5 | import 'package:generator_app/foo/foo_widget.story.dart' as _i5; 6 | import 'package:generator_app/image/asset_image.story.dart' as _i4; 7 | import 'package:generator_app/page/page.story.dart' as _i3; 8 | import 'package:generator_app/scrollable/scrollable.story.dart' as _i6; 9 | import 'package:playbook/playbook.dart' as _i1; 10 | import 'package:playbook/src/scenario_layout.dart' as _i2; 11 | 12 | _i1.Playbook get playbook => _i1.Playbook(stories: stories); 13 | List<_i1.Story> get stories => [ 14 | _$page$page$Story(), 15 | _$image$asset_image$Story(), 16 | _$foo$foo_widget$Story(), 17 | _$scrollable$scrollable$Story(), 18 | _$bar$bar$Story(), 19 | ]; 20 | _i1.Story _$page$page$Story() => _i1.Story( 21 | 'HomePage', 22 | scenarios: [ 23 | _i1.Scenario( 24 | 'myPage', 25 | layout: _i2.ScenarioLayout.compressed(), 26 | child: _i3.myPage(), 27 | ), 28 | ], 29 | ); 30 | _i1.Story _$image$asset_image$Story() => _i1.Story( 31 | 'AssetImage', 32 | scenarios: [ 33 | _i1.Scenario( 34 | 'AssetImage', 35 | layout: _i2.ScenarioLayout.compressed(), 36 | child: _i4.$AssetImage(), 37 | ), 38 | _i1.Scenario( 39 | 'MaterialIcon', 40 | layout: _i2.ScenarioLayout.compressed(), 41 | child: _i4.$MaterialIcon(), 42 | ), 43 | _i1.Scenario( 44 | 'CupertinoIcon', 45 | layout: _i2.ScenarioLayout.compressed(), 46 | child: _i4.$CupertinoIcon(), 47 | ), 48 | ], 49 | ); 50 | _i1.Story _$foo$foo_widget$Story() => _i1.Story( 51 | 'FooWidget', 52 | scenarios: [ 53 | _i1.Scenario( 54 | 'foo3foo3', 55 | layout: _i2.ScenarioLayout.sizing( 56 | _i2.ScenarioLayoutFixed(100.0), 57 | _i2.ScenarioLayoutFixed(200.0), 58 | ), 59 | child: _i5.Foo3(), 60 | ), 61 | _i1.Scenario( 62 | 'Foo4 Bar', 63 | layout: _i2.ScenarioLayout.fixedH( 64 | 300.0, 65 | crossAxisLayout: _i2.ScenarioLayoutFill(), 66 | ), 67 | child: _i5.$Foo4_Bar(), 68 | ), 69 | _i1.Scenario( 70 | 'foo5', 71 | layout: _i2.ScenarioLayout.fixedV( 72 | 500.0, 73 | crossAxisLayout: _i2.ScenarioLayoutFixed(200.0), 74 | ), 75 | child: _i5.$foo5(), 76 | ), 77 | _i5.foo1(), 78 | _i5.foo2(), 79 | ], 80 | ); 81 | _i1.Story _$scrollable$scrollable$Story() => _i1.Story( 82 | 'scrollableStory', 83 | scenarios: [ 84 | _i1.Scenario( 85 | 'Primary Scrollable', 86 | layout: _i2.ScenarioLayout.compressed(), 87 | child: _i6.primary(), 88 | ), 89 | _i1.Scenario( 90 | 'Not Primary Scrollable', 91 | layout: _i2.ScenarioLayout.compressed(), 92 | child: _i6.notPrimary(), 93 | ), 94 | ], 95 | ); 96 | _i1.Story _$bar$bar$Story() => _i1.Story( 97 | 'BarWidget', 98 | scenarios: [_i7.twoLineText(), _i7.emptyText(), ..._i7.variousText()], 99 | ); 100 | -------------------------------------------------------------------------------- /examples/generator_app/lib/image/asset_image.story.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:playbook/playbook.dart'; 4 | 5 | const storyTitle = 'AssetImage'; 6 | 7 | @GenerateScenario() 8 | Widget $AssetImage() => Image.asset('assets/flutter.png'); 9 | 10 | @GenerateScenario() 11 | Widget $MaterialIcon() => const Icon(Icons.access_alarm); 12 | 13 | @GenerateScenario() 14 | Widget $CupertinoIcon() => const Icon(CupertinoIcons.alarm); 15 | -------------------------------------------------------------------------------- /examples/generator_app/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:generator_app/bar/bar.dart'; 3 | 4 | void main() { 5 | runApp(const MyApp()); 6 | } 7 | 8 | class MyApp extends StatelessWidget { 9 | const MyApp({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return MaterialApp( 14 | title: 'Flutter Demo', 15 | theme: ThemeData( 16 | primarySwatch: Colors.blue, 17 | ), 18 | home: const BarWidget(text: 'Sample app'), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/generator_app/lib/main_catalog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:generator_app/generated_playbook.dart'; 3 | import 'package:playbook_ui/playbook_ui.dart'; 4 | 5 | void main() { 6 | runApp(const MyApp()); 7 | } 8 | 9 | class MyApp extends StatefulWidget { 10 | const MyApp({super.key}); 11 | 12 | @override 13 | State createState() => _MyAppState(); 14 | } 15 | 16 | class _MyAppState extends State { 17 | var _isDark = false; 18 | final controller = TextEditingController(); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return MaterialApp( 23 | title: 'Playbook Demo', 24 | theme: _isDark ? ThemeData.dark() : ThemeData.light(), 25 | home: PlaybookGallery( 26 | title: 'Generator app', 27 | searchTextController: controller, 28 | checkeredColor: null, 29 | onCustomActionPressed: () => setState(() { 30 | _isDark = !_isDark; 31 | }), 32 | playbook: playbook, 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/generator_app/lib/page/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HomePage extends StatefulWidget { 4 | const HomePage({super.key, required this.title}); 5 | 6 | final String title; 7 | 8 | @override 9 | State createState() => _MyPageState(); 10 | } 11 | 12 | class _MyPageState extends State { 13 | int _counter = 0; 14 | 15 | void _incrementCounter() { 16 | setState(() { 17 | _counter += 1; 18 | }); 19 | } 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | appBar: AppBar( 25 | backgroundColor: Theme.of(context).colorScheme.inversePrimary, 26 | title: Text(widget.title), 27 | ), 28 | body: Center( 29 | child: Column( 30 | mainAxisAlignment: MainAxisAlignment.center, 31 | children: [ 32 | const Text( 33 | 'You have pushed the button this many times:', 34 | ), 35 | Text(_counter.toString()), 36 | ], 37 | ), 38 | ), 39 | floatingActionButton: FloatingActionButton( 40 | onPressed: _incrementCounter, 41 | tooltip: 'Increment', 42 | child: const Icon(Icons.add), 43 | ), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/generator_app/lib/page/page.story.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:generator_app/page/page.dart'; 3 | import 'package:playbook/playbook.dart'; 4 | 5 | const storyTitle = 'HomePage'; 6 | 7 | @GenerateScenario() 8 | Widget myPage() => const HomePage(title: 'Home Page'); 9 | -------------------------------------------------------------------------------- /examples/generator_app/lib/scrollable/scrollable.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Widget scrollable({required bool primary}) => Row( 4 | children: [ 5 | Flexible( 6 | child: ListView.builder( 7 | primary: primary, 8 | itemBuilder: (context, index) => Material( 9 | type: MaterialType.transparency, 10 | child: ListTile( 11 | title: Text('Item $index'), 12 | ), 13 | ), 14 | itemCount: 40, 15 | ), 16 | ), 17 | Flexible( 18 | child: ListView.builder( 19 | primary: primary, 20 | itemBuilder: (context, index) => Material( 21 | type: MaterialType.transparency, 22 | child: ListTile( 23 | title: Text('Item $index'), 24 | ), 25 | ), 26 | itemCount: 10, 27 | ), 28 | ), 29 | ], 30 | ); 31 | -------------------------------------------------------------------------------- /examples/generator_app/lib/scrollable/scrollable.story.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:generator_app/scrollable/scrollable.dart'; 3 | import 'package:playbook/playbook.dart'; 4 | 5 | const storyTitle = 'scrollableStory'; 6 | 7 | @GenerateScenario(title: 'Primary Scrollable') 8 | Widget primary() => scrollable(primary: true); 9 | 10 | @GenerateScenario(title: 'Not Primary Scrollable') 11 | Widget notPrimary() => scrollable(primary: false); 12 | -------------------------------------------------------------------------------- /examples/generator_app/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: generator_app 2 | description: Sample app 3 | version: 1.0.0+1 4 | 5 | publish_to: "none" 6 | 7 | environment: 8 | sdk: ">=3.6.0 <4.0.0" 9 | flutter: ">=3.27.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | cupertino_icons: ^1.0.6 15 | playbook: 16 | playbook_ui: 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | build_runner: ^2.0.0 22 | playbook_generator: 23 | playbook_snapshot: 24 | 25 | flutter: 26 | uses-material-design: true 27 | assets: 28 | - assets/flutter.png 29 | 30 | playbook_snapshot: 31 | snapshot_dir: screenshots 32 | fonts: 33 | - family: Roboto 34 | fonts: 35 | - asset: assets/fonts/Roboto-Regular.ttf 36 | -------------------------------------------------------------------------------- /examples/generator_app/pubspec_overrides.yaml: -------------------------------------------------------------------------------- 1 | # melos_managed_dependency_overrides: playbook,playbook_generator,playbook_ui,playbook_snapshot 2 | dependency_overrides: 3 | playbook: 4 | path: ../../packages/playbook 5 | playbook_generator: 6 | path: ../../packages/playbook_generator 7 | playbook_snapshot: 8 | path: ../../packages/playbook_snapshot 9 | playbook_ui: 10 | path: ../../packages/playbook_ui 11 | -------------------------------------------------------------------------------- /examples/generator_app/test/snapshot_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:generator_app/generated_playbook.dart'; 4 | import 'package:playbook_snapshot/playbook_snapshot.dart'; 5 | 6 | Future main() async { 7 | testWidgets('Take snapshots', (tester) async { 8 | await playbook.run( 9 | const Snapshot( 10 | devices: [ 11 | SnapshotDevice.iPhoneSE2nd, 12 | ], 13 | ), 14 | tester, 15 | (widget, device) { 16 | return MaterialApp( 17 | debugShowCheckedModeBanner: false, 18 | theme: ThemeData( 19 | fontFamily: 'Roboto', 20 | platform: device.platform, 21 | ), 22 | home: widget, 23 | ); 24 | }, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /examples/simple_app/README.md: -------------------------------------------------------------------------------- 1 | # sample app 2 | -------------------------------------------------------------------------------- /examples/simple_app/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 | -------------------------------------------------------------------------------- /examples/simple_app/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | namespace "com.example.simple_app" 27 | compileSdk flutter.compileSdkVersion 28 | ndkVersion flutter.ndkVersion 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 45 | applicationId "com.example.simple_app" 46 | // You can update the following values to match your application needs. 47 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 48 | minSdkVersion flutter.minSdkVersion 49 | targetSdkVersion flutter.targetSdkVersion 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | } 53 | 54 | buildTypes { 55 | release { 56 | // TODO: Add your own signing config for the release build. 57 | // Signing with the debug keys for now, so `flutter run --release` works. 58 | signingConfig signingConfigs.debug 59 | } 60 | } 61 | } 62 | 63 | flutter { 64 | source '../..' 65 | } 66 | 67 | dependencies {} 68 | -------------------------------------------------------------------------------- /examples/simple_app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/simple_app/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/simple_app/android/app/src/main/kotlin/com/example/simple_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.simple_app 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /examples/simple_app/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /examples/simple_app/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /examples/simple_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/simple_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/simple_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/simple_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/simple_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/simple_app/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /examples/simple_app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /examples/simple_app/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/simple_app/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /examples/simple_app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /examples/simple_app/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.6.3-all.zip 6 | -------------------------------------------------------------------------------- /examples/simple_app/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | } 19 | 20 | plugins { 21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 22 | id "com.android.application" version "7.3.0" apply false 23 | id "org.jetbrains.kotlin.android" version "1.7.10" apply false 24 | } 25 | 26 | include ":app" 27 | -------------------------------------------------------------------------------- /examples/simple_app/assets/flutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/assets/flutter.png -------------------------------------------------------------------------------- /examples/simple_app/assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /examples/simple_app/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 | -------------------------------------------------------------------------------- /examples/simple_app/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 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/simple_app/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 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /examples/simple_app/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 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playbook-ui/playbook-flutter/f9f7903e668257faf794cf15d96f9d4ec2b1cb82/examples/simple_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /examples/simple_app/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. -------------------------------------------------------------------------------- /examples/simple_app/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 | -------------------------------------------------------------------------------- /examples/simple_app/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 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Simple App 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | simple_app 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 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /examples/simple_app/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /examples/simple_app/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /examples/simple_app/lib/bar/bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BarWidget extends StatelessWidget { 4 | const BarWidget({ 5 | super.key, 6 | required this.text, 7 | }); 8 | 9 | final String text; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return ColoredBox( 14 | color: Colors.amberAccent, 15 | child: Row( 16 | mainAxisAlignment: MainAxisAlignment.center, 17 | children: [ 18 | const Icon(Icons.star), 19 | const SizedBox(width: 16), 20 | Text(text, style: Theme.of(context).textTheme.headlineSmall), 21 | ], 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/simple_app/lib/bar/bar.story.dart: -------------------------------------------------------------------------------- 1 | import 'package:playbook/playbook.dart'; 2 | import 'package:simple_app/bar/bar.dart'; 3 | 4 | Scenario twoLineText() { 5 | return const Scenario( 6 | 'TwoLine', 7 | child: BarWidget(text: 'TextTextText\nTextText'), 8 | ); 9 | } 10 | 11 | Scenario emptyText() { 12 | return const Scenario('Empty', child: BarWidget(text: '')); 13 | } 14 | 15 | List variousText() { 16 | return ['Text 1', 'Text2'] 17 | .map( 18 | (e) => Scenario(e, child: BarWidget(text: e)), 19 | ) 20 | .toList(); 21 | } 22 | 23 | Story barStory() { 24 | return Story( 25 | 'BarWidget', 26 | scenarios: [ 27 | twoLineText(), 28 | emptyText(), 29 | ...variousText(), 30 | ], 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /examples/simple_app/lib/foo/foo_widget.story.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: camel_case_types 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:playbook/playbook.dart'; 5 | 6 | Scenario foo1() { 7 | return Scenario('Hello', child: Container(color: Colors.blue)); 8 | } 9 | 10 | Scenario foo2() { 11 | return Scenario( 12 | 'World', 13 | alignment: Alignment.topRight, 14 | child: Center( 15 | child: Container(width: 200, height: 250, color: Colors.amber), 16 | ), 17 | ); 18 | } 19 | 20 | class Foo3 extends StatelessWidget { 21 | const Foo3({super.key}); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Container(color: Colors.amber); 26 | } 27 | } 28 | 29 | class Foo4_Bar extends StatelessWidget { 30 | const Foo4_Bar({super.key}); 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Container(color: Colors.pink); 35 | } 36 | } 37 | 38 | Widget foo5() => const Text('Foo 5'); 39 | 40 | Story fooWidgetStory() { 41 | return Story( 42 | 'FooWidget', 43 | scenarios: [ 44 | foo1(), 45 | foo2(), 46 | const Scenario( 47 | 'foo3foo3', 48 | layout: ScenarioLayout.sizing( 49 | ScenarioLayoutFixed( 50 | 100, 51 | ), 52 | ScenarioLayoutFixed( 53 | 200, 54 | ), 55 | ), 56 | child: Foo3(), 57 | ), 58 | const Scenario( 59 | 'Foo4 Bar', 60 | layout: ScenarioLayout.fixedH( 61 | 300, 62 | crossAxisLayout: ScenarioLayoutFill(), 63 | ), 64 | child: Foo4_Bar(), 65 | ), 66 | Scenario( 67 | 'foo5', 68 | layout: const ScenarioLayout.fixedV( 69 | 500, 70 | crossAxisLayout: ScenarioLayoutFixed( 71 | 200, 72 | ), 73 | ), 74 | child: foo5(), 75 | ), 76 | ], 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /examples/simple_app/lib/image/asset_image.story.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:playbook/playbook.dart'; 4 | 5 | Widget assetImage() => Image.asset('assets/flutter.png'); 6 | 7 | Story assetImageStory() { 8 | return Story( 9 | 'AssetImage', 10 | scenarios: [ 11 | Scenario( 12 | 'AssetImage', 13 | child: assetImage(), 14 | ), 15 | const Scenario( 16 | 'MaterialIcon', 17 | child: Icon(Icons.access_alarm), 18 | ), 19 | const Scenario( 20 | 'CupertinoIcon', 21 | child: Icon(CupertinoIcons.alarm), 22 | ), 23 | ], 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /examples/simple_app/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:simple_app/bar/bar.dart'; 3 | 4 | void main() { 5 | runApp(const MyApp()); 6 | } 7 | 8 | class MyApp extends StatelessWidget { 9 | const MyApp({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return MaterialApp( 14 | title: 'Flutter Demo', 15 | theme: ThemeData( 16 | primarySwatch: Colors.blue, 17 | ), 18 | home: const BarWidget(text: 'Sample app'), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/simple_app/lib/main_catalog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:playbook/playbook.dart'; 3 | import 'package:playbook_ui/playbook_ui.dart'; 4 | import 'package:simple_app/bar/bar.story.dart'; 5 | import 'package:simple_app/foo/foo_widget.story.dart'; 6 | import 'package:simple_app/image/asset_image.story.dart'; 7 | import 'package:simple_app/page/page.story.dart'; 8 | import 'package:simple_app/scrollable/scrollable.story.dart'; 9 | 10 | void main() { 11 | runApp(const MyApp()); 12 | } 13 | 14 | class MyApp extends StatefulWidget { 15 | const MyApp({super.key}); 16 | 17 | @override 18 | State createState() => _MyAppState(); 19 | } 20 | 21 | class _MyAppState extends State { 22 | var _isDark = false; 23 | final controller = TextEditingController(); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return MaterialApp( 28 | title: 'Playbook Demo', 29 | theme: _isDark ? ThemeData.dark() : ThemeData.light(), 30 | home: PlaybookGallery( 31 | title: 'Sample app', 32 | searchTextController: controller, 33 | onCustomActionPressed: () => setState(() { 34 | _isDark = !_isDark; 35 | }), 36 | playbook: Playbook( 37 | stories: [ 38 | barStory(), 39 | fooWidgetStory(), 40 | assetImageStory(), 41 | homePageStory(), 42 | scrollableStory(), 43 | ], 44 | ), 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/simple_app/lib/page/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HomePage extends StatefulWidget { 4 | const HomePage({super.key, required this.title}); 5 | 6 | final String title; 7 | 8 | @override 9 | State createState() => _MyPageState(); 10 | } 11 | 12 | class _MyPageState extends State { 13 | int _counter = 0; 14 | 15 | void _incrementCounter() { 16 | setState(() { 17 | _counter += 1; 18 | }); 19 | } 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | appBar: AppBar( 25 | backgroundColor: Theme.of(context).colorScheme.inversePrimary, 26 | title: Text(widget.title), 27 | ), 28 | body: Center( 29 | child: Column( 30 | mainAxisAlignment: MainAxisAlignment.center, 31 | children: [ 32 | const Text( 33 | 'You have pushed the button this many times:', 34 | ), 35 | Text(_counter.toString()), 36 | ], 37 | ), 38 | ), 39 | floatingActionButton: FloatingActionButton( 40 | onPressed: _incrementCounter, 41 | tooltip: 'Increment', 42 | child: const Icon(Icons.add), 43 | ), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/simple_app/lib/page/page.story.dart: -------------------------------------------------------------------------------- 1 | import 'package:playbook/playbook.dart'; 2 | import 'package:simple_app/page/page.dart'; 3 | 4 | Story homePageStory() { 5 | return const Story( 6 | 'HomePage', 7 | scenarios: [ 8 | Scenario( 9 | 'myPage', 10 | child: HomePage(title: 'Home Page'), 11 | ), 12 | ], 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /examples/simple_app/lib/scrollable/scrollable.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Widget scrollable({required bool primary}) => Row( 4 | children: [ 5 | Flexible( 6 | child: ListView.builder( 7 | primary: primary, 8 | itemBuilder: (context, index) => Material( 9 | type: MaterialType.transparency, 10 | child: ListTile( 11 | title: Text('Item $index'), 12 | ), 13 | ), 14 | itemCount: 40, 15 | ), 16 | ), 17 | Flexible( 18 | child: ListView.builder( 19 | primary: primary, 20 | itemBuilder: (context, index) => Material( 21 | type: MaterialType.transparency, 22 | child: ListTile( 23 | title: Text('Item $index'), 24 | ), 25 | ), 26 | itemCount: 10, 27 | ), 28 | ), 29 | ], 30 | ); 31 | -------------------------------------------------------------------------------- /examples/simple_app/lib/scrollable/scrollable.story.dart: -------------------------------------------------------------------------------- 1 | import 'package:playbook/playbook.dart'; 2 | import 'package:simple_app/scrollable/scrollable.dart'; 3 | 4 | Story scrollableStory() { 5 | return Story( 6 | 'Scrollable', 7 | scenarios: [ 8 | Scenario( 9 | 'Primary Scrollable', 10 | child: scrollable(primary: true), 11 | ), 12 | Scenario( 13 | 'Not Primary Scrollable', 14 | child: scrollable(primary: false), 15 | ), 16 | ], 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /examples/simple_app/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: simple_app 2 | description: Sample app 3 | version: 1.0.0+1 4 | 5 | publish_to: "none" 6 | 7 | environment: 8 | sdk: ">=3.6.0 <4.0.0" 9 | flutter: ">=3.27.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | cupertino_icons: ^1.0.6 15 | playbook: 16 | playbook_ui: 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | playbook_snapshot: 22 | 23 | flutter: 24 | uses-material-design: true 25 | 26 | assets: 27 | - assets/flutter.png 28 | 29 | playbook_snapshot: 30 | snapshot_dir: screenshots 31 | fonts: 32 | - family: Roboto 33 | fonts: 34 | - asset: assets/fonts/Roboto-Regular.ttf 35 | -------------------------------------------------------------------------------- /examples/simple_app/pubspec_overrides.yaml: -------------------------------------------------------------------------------- 1 | # melos_managed_dependency_overrides: playbook,playbook_snapshot,playbook_ui 2 | dependency_overrides: 3 | playbook: 4 | path: ../../packages/playbook 5 | playbook_snapshot: 6 | path: ../../packages/playbook_snapshot 7 | playbook_ui: 8 | path: ../../packages/playbook_ui 9 | -------------------------------------------------------------------------------- /examples/simple_app/test/snapshot_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:playbook/playbook.dart'; 4 | import 'package:playbook_snapshot/playbook_snapshot.dart'; 5 | import 'package:simple_app/bar/bar.story.dart'; 6 | import 'package:simple_app/foo/foo_widget.story.dart'; 7 | import 'package:simple_app/image/asset_image.story.dart'; 8 | import 'package:simple_app/page/page.story.dart'; 9 | import 'package:simple_app/scrollable/scrollable.story.dart'; 10 | 11 | Future main() async { 12 | testWidgets('Take snapshots', (tester) async { 13 | await Playbook( 14 | stories: [ 15 | barStory(), 16 | fooWidgetStory(), 17 | assetImageStory(), 18 | homePageStory(), 19 | scrollableStory(), 20 | ], 21 | ).run( 22 | const Snapshot( 23 | devices: [ 24 | SnapshotDevice.iPhoneSE2nd, 25 | ], 26 | ), 27 | tester, 28 | (widget, device) { 29 | return MaterialApp( 30 | debugShowCheckedModeBanner: false, 31 | theme: ThemeData( 32 | fontFamily: 'Roboto', 33 | platform: device.platform, 34 | ), 35 | home: widget, 36 | ); 37 | }, 38 | ); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /melos.yaml: -------------------------------------------------------------------------------- 1 | name: Playbook 2 | repository: https://github.com/playbook-ui/playbook-flutter 3 | 4 | packages: 5 | - packages/** 6 | - examples/** 7 | 8 | ide: 9 | intellij: true 10 | 11 | command: 12 | version: 13 | linkToCommits: true 14 | 15 | bootstrap: 16 | hooks: 17 | post: 18 | run: | 19 | melos gen 20 | 21 | scripts: 22 | format: melos exec -- flutter format . 23 | 24 | analyze: melos exec -- flutter analyze . 25 | 26 | clean: melos exec -- flutter clean 27 | 28 | upgrade: 29 | run: | 30 | melos exec -- flutter pub upgrade 31 | melos bootstrap 32 | 33 | get: 34 | run: melos exec -- flutter pub get 35 | 36 | gen: 37 | run: melos gen:all --no-select 38 | 39 | gen:all: 40 | run: | 41 | melos exec -- dart run build_runner build --delete-conflicting-outputs 42 | packageFilters: 43 | dependsOn: build_runner 44 | 45 | test: 46 | run: melos test:snapshot --no-select 47 | 48 | test:snapshot: 49 | run: | 50 | melos exec -- flutter test --update-goldens 51 | packageFilters: 52 | dependsOn: 53 | - playbook_snapshot 54 | dirExists: 55 | - test 56 | 57 | test:all: 58 | run: | 59 | melos exec -- flutter test --update-goldens 60 | packageFilters: 61 | dirExists: 62 | - test 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "reg-suit": "^0.14.0" 4 | }, 5 | "devDependencies": { 6 | "reg-keygen-git-hash-plugin": "^0.14.0", 7 | "reg-notify-github-plugin": "^0.14.0", 8 | "reg-publish-gcs-plugin": "^0.14.0" 9 | }, 10 | "scripts": { 11 | "regression": "reg-suit run" 12 | } 13 | } -------------------------------------------------------------------------------- /packages/playbook/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.1.0 2 | 3 | - **FEAT**: can set `ScenarioWidgetBuilder` to `ScenarioWidget`. 4 | 5 | ## 1.0.1 6 | 7 | - **FIX**: can set `ScenarioLayoutSizing` to `ScenarioLayout`. 8 | 9 | ## 1.0.0 10 | 11 | - **BREAKING CHANGE**: remove `needsScrollableResizing` from `ScenarioLayoutCompressed`. Use `ScenarioLayoutFill` instead. 12 | 13 | ## 0.1.0 14 | 15 | - **FEAT**: `Scenario` is supported to build with `WidgetBuilder`. 16 | - **BREAKING CHANGE**: upgrade sdk and flutter. 17 | 18 | ## 0.0.4 19 | 20 | - **BREAKING CHANGE**: remove scenario thumbnail scale parameter. 21 | 22 | ## 0.0.3 23 | 24 | - **FEAT**: can be stop resizing `Scrollable`. 25 | 26 | ## 0.0.2 27 | 28 | - **BUILD**: bump deps. 29 | 30 | ## [0.0.1+1] 31 | 32 | - Delete unnecessary dependencies. 33 | 34 | ## [0.0.1] 35 | 36 | - initial release. 37 | -------------------------------------------------------------------------------- /packages/playbook/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /packages/playbook/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /packages/playbook/lib/playbook.dart: -------------------------------------------------------------------------------- 1 | export 'playbook_annotations.dart'; 2 | export 'src/playbook.dart'; 3 | export 'src/scenario.dart'; 4 | export 'src/scenario_layout.dart'; 5 | export 'src/scenario_widget.dart'; 6 | export 'src/story.dart'; 7 | -------------------------------------------------------------------------------- /packages/playbook/lib/playbook_annotations.dart: -------------------------------------------------------------------------------- 1 | export 'src/generate_scenario.dart'; 2 | -------------------------------------------------------------------------------- /packages/playbook/lib/src/generate_scenario.dart: -------------------------------------------------------------------------------- 1 | import 'package:playbook/src/scenario_layout.dart'; 2 | 3 | // TODO: alignment 4 | class GenerateScenario { 5 | const GenerateScenario({ 6 | this.title, 7 | this.layout = const ScenarioLayout.compressed(), 8 | }); 9 | 10 | final String? title; 11 | final ScenarioLayout layout; 12 | } 13 | -------------------------------------------------------------------------------- /packages/playbook/lib/src/playbook.dart: -------------------------------------------------------------------------------- 1 | import 'package:playbook/src/scenario.dart'; 2 | import 'package:playbook/src/story.dart'; 3 | 4 | class Playbook { 5 | const Playbook({ 6 | List? stories, 7 | }) : stories = stories ?? const []; 8 | 9 | final List stories; 10 | 11 | void addScenariosOf(String title, {required List scenarios}) { 12 | stories.add(Story(title, scenarios: scenarios)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/playbook/lib/src/scenario.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:playbook/src/scenario_layout.dart'; 3 | 4 | class Scenario { 5 | const Scenario( 6 | this.title, { 7 | this.layout = const ScenarioLayout.compressed(), 8 | this.alignment = Alignment.center, 9 | required this.child, 10 | }); 11 | 12 | Scenario.builder( 13 | this.title, { 14 | this.layout = const ScenarioLayout.compressed(), 15 | this.alignment = Alignment.center, 16 | required WidgetBuilder builder, 17 | }) : child = Builder(builder: builder); 18 | 19 | final String title; 20 | final ScenarioLayout layout; 21 | final AlignmentGeometry alignment; 22 | final Widget child; 23 | } 24 | -------------------------------------------------------------------------------- /packages/playbook/lib/src/scenario_layout.dart: -------------------------------------------------------------------------------- 1 | abstract class ScenarioLayoutSizing { 2 | const ScenarioLayoutSizing(); 3 | } 4 | 5 | class ScenarioLayoutCompressed extends ScenarioLayoutSizing { 6 | const ScenarioLayoutCompressed(); 7 | } 8 | 9 | class ScenarioLayoutFill extends ScenarioLayoutSizing { 10 | const ScenarioLayoutFill(); 11 | } 12 | 13 | class ScenarioLayoutFixed extends ScenarioLayoutSizing { 14 | const ScenarioLayoutFixed(this.value); 15 | 16 | final double value; 17 | } 18 | 19 | class ScenarioLayout { 20 | const ScenarioLayout.fill({ 21 | ScenarioLayoutSizing horizontalLayout = const ScenarioLayoutFill(), 22 | ScenarioLayoutSizing verticalLayout = const ScenarioLayoutFill(), 23 | }) : _dh = null, 24 | _dv = null, 25 | _h = horizontalLayout, 26 | _v = verticalLayout; 27 | 28 | const ScenarioLayout.compressed({ 29 | ScenarioLayoutSizing horizontalLayout = const ScenarioLayoutCompressed(), 30 | ScenarioLayoutSizing verticalLayout = const ScenarioLayoutCompressed(), 31 | }) : _dh = null, 32 | _dv = null, 33 | _h = horizontalLayout, 34 | _v = verticalLayout; 35 | 36 | const ScenarioLayout.fixed(double width, double height) 37 | : _dh = width, 38 | _dv = height, 39 | _h = null, 40 | _v = null; 41 | 42 | const ScenarioLayout.fixedH( 43 | double width, { 44 | ScenarioLayoutSizing crossAxisLayout = const ScenarioLayoutCompressed(), 45 | }) : _dh = width, 46 | _dv = null, 47 | _h = null, 48 | _v = crossAxisLayout; 49 | 50 | const ScenarioLayout.fixedV( 51 | double height, { 52 | ScenarioLayoutSizing crossAxisLayout = const ScenarioLayoutCompressed(), 53 | }) : _dh = null, 54 | _dv = height, 55 | _h = crossAxisLayout, 56 | _v = null; 57 | 58 | const ScenarioLayout.sizing(this._h, this._v) 59 | : _dh = null, 60 | _dv = null; 61 | 62 | final double? _dh; 63 | final double? _dv; 64 | 65 | final ScenarioLayoutSizing? _h; 66 | final ScenarioLayoutSizing? _v; 67 | 68 | ScenarioLayoutSizing get h => _h ?? ScenarioLayoutFixed(_dh!); 69 | 70 | ScenarioLayoutSizing get v => _v ?? ScenarioLayoutFixed(_dv!); 71 | } 72 | -------------------------------------------------------------------------------- /packages/playbook/lib/src/scenario_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:playbook/src/scenario.dart'; 3 | 4 | typedef ScenarioWidgetBuilder = Widget Function( 5 | BuildContext context, 6 | Widget child, 7 | ); 8 | 9 | class ScenarioWidget extends StatelessWidget { 10 | const ScenarioWidget({ 11 | super.key, 12 | required this.scenario, 13 | this.canvasColor, 14 | this.checkeredColor, 15 | this.checkeredRectSize, 16 | this.builder, 17 | required this.useMaterial, 18 | }); 19 | 20 | final Color? canvasColor; 21 | final Color? checkeredColor; 22 | final double? checkeredRectSize; 23 | final bool useMaterial; 24 | final Scenario scenario; 25 | final ScenarioWidgetBuilder? builder; 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | final child = Align( 30 | alignment: scenario.alignment, 31 | child: builder?.call(context, scenario.child) ?? scenario.child, 32 | ); 33 | 34 | final color = checkeredColor != null ? canvasColor : null; 35 | final content = checkeredColor != null 36 | ? Checkered( 37 | rectSize: checkeredRectSize ?? 5, 38 | color: checkeredColor!, 39 | child: child, 40 | ) 41 | : child; 42 | 43 | return useMaterial 44 | ? Material( 45 | color: color, 46 | child: content, 47 | ) 48 | : Container( 49 | color: color, 50 | child: content, 51 | ); 52 | } 53 | } 54 | 55 | class Checkered extends StatelessWidget { 56 | const Checkered({ 57 | super.key, 58 | required this.rectSize, 59 | required this.color, 60 | required this.child, 61 | }); 62 | 63 | final double rectSize; 64 | final Color color; 65 | final Widget child; 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | return CustomPaint( 70 | painter: CheckeredPainter( 71 | rectSize: rectSize, 72 | color: color, 73 | ), 74 | child: child, 75 | ); 76 | } 77 | } 78 | 79 | class CheckeredPainter extends CustomPainter { 80 | const CheckeredPainter({ 81 | super.repaint, 82 | required this.rectSize, 83 | required this.color, 84 | }); 85 | 86 | final double rectSize; 87 | final Color color; 88 | 89 | @override 90 | void paint(Canvas canvas, Size size) { 91 | final h = List.generate(size.height ~/ rectSize + 1, (i) => i * rectSize); 92 | final w = List.generate(size.width ~/ rectSize + 1, (i) => i * rectSize); 93 | final rectsPath = Path(); 94 | 95 | for (final y in h) { 96 | for (final x in w) { 97 | if ((x ~/ rectSize).isOdd == (y ~/ rectSize).isOdd) { 98 | rectsPath.addRect(Rect.fromLTWH(x, y, rectSize, rectSize)); 99 | } 100 | } 101 | } 102 | 103 | final paint = Paint() 104 | ..color = color 105 | ..style = PaintingStyle.fill; 106 | canvas.drawPath(rectsPath, paint); 107 | } 108 | 109 | @override 110 | bool shouldRepaint(covariant CustomPainter oldDelegate) => false; 111 | } 112 | -------------------------------------------------------------------------------- /packages/playbook/lib/src/story.dart: -------------------------------------------------------------------------------- 1 | import 'package:playbook/src/scenario.dart'; 2 | 3 | class Story { 4 | const Story( 5 | this.title, { 6 | this.scenarios = const [], 7 | }); 8 | 9 | final String title; 10 | final List scenarios; 11 | } 12 | -------------------------------------------------------------------------------- /packages/playbook/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: playbook 2 | description: Playbook package. 3 | version: 1.1.0 4 | repository: https://github.com/playbook-ui/playbook-flutter 5 | documentation: https://github.com/playbook-ui/playbook-flutter 6 | issue_tracker: https://github.com/playbook-ui/playbook-flutter/issues 7 | 8 | environment: 9 | sdk: ">=3.6.0 <4.0.0" 10 | flutter: ">=3.27.0" 11 | 12 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | very_good_analysis: ^7.0.0 20 | 21 | flutter: 22 | uses-material-design: true 23 | -------------------------------------------------------------------------------- /packages/playbook/test/playbook_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | test('adds one to input values', () {}); 5 | } 6 | -------------------------------------------------------------------------------- /packages/playbook_generator/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.1.0 2 | 3 | - upgrade analyzer to greater than 7.4.5. 4 | 5 | ## 1.0.0 6 | 7 | - **BREAKING CHANGE**: remove `needsScrollableResizing` from `ScenarioLayoutCompressed`. Use `ScenarioLayoutFill` instead. 8 | - **FEAT**: can be set to find assets glob and output file name. 9 | 10 | ## 0.1.0 11 | 12 | - **FEAT**: generate `Scenario` with the function that has `BuildContext`. 13 | - **BREAKING CHANGE**: upgrade sdk with some dependencies. 14 | 15 | ## 0.0.6+1 16 | 17 | - **CHORE**: fix analyzer version. 18 | 19 | ## 0.0.6 20 | 21 | - **FEAT**: bump analyzer ([#57](https://github.com/playbook-ui/playbook-flutter/issues/57)). ([e1fe1430](https://github.com/playbook-ui/playbook-flutter/commit/e1fe1430f3d91b8ab129626c3858345c8955b573)) 22 | 23 | ## 0.0.5 24 | 25 | - **BREAKING CHANGE**: remove scenario thumbnail scale parameter. 26 | 27 | ## 0.0.4+1 28 | 29 | - upgrade dependencies 30 | 31 | ## 0.0.4 32 | 33 | - **FIX**: fix searching for generate files. 34 | 35 | ## 0.0.3+1 36 | 37 | - **REFACTOR**: generator not depended on playbook anymore. ([9a46a233](https://github.com/playbook-ui/playbook-flutter/commit/9a46a2335d4934158c840da39fc3743b9959fe67)) 38 | 39 | ## 0.0.3 40 | 41 | - **FEAT**: remove dartx and use dart api. 42 | 43 | ## 0.0.2 44 | 45 | - upgrade dependencies 46 | 47 | ## 0.0.1 48 | 49 | - initial release. 50 | -------------------------------------------------------------------------------- /packages/playbook_generator/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /packages/playbook_generator/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /packages/playbook_generator/build.yaml: -------------------------------------------------------------------------------- 1 | builders: 2 | stories: 3 | import: "package:playbook_generator/playbook_generator.dart" 4 | builder_factories: ["playbookBuilder"] 5 | build_extensions: { "$lib$": [".dart"] } 6 | auto_apply: dependents 7 | build_to: source 8 | -------------------------------------------------------------------------------- /packages/playbook_generator/lib/playbook_generator.dart: -------------------------------------------------------------------------------- 1 | import 'package:build/build.dart'; 2 | import 'package:playbook_generator/src/playbook_builder.dart'; 3 | 4 | Builder playbookBuilder(BuilderOptions options) => 5 | PlaybookBuilder(options.config); 6 | -------------------------------------------------------------------------------- /packages/playbook_generator/lib/src/constant_reader_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:code_builder/code_builder.dart'; 2 | import 'package:source_gen/source_gen.dart'; 3 | 4 | /// Currently only for ScenarioLayout 5 | String constantReaderToSource( 6 | ConstantReader reader, 7 | String Function(Reference) allocator, 8 | ) { 9 | if (reader.isString) { 10 | return "'${reader.stringValue}'"; 11 | } else if (reader.isDouble) { 12 | return reader.doubleValue.toString(); 13 | } else { 14 | final revivable = reader.revive(); 15 | final accessor = revivable.accessor.let((it) => it.isEmpty ? '' : '.$it'); 16 | final constructor = '${revivable.source.fragment}$accessor'; 17 | final url = 'package:${revivable.source.path.replaceFirst('lib/', '')}'; 18 | final constructorRefer = allocator(refer(constructor, url)); 19 | final positions = revivable.positionalArguments 20 | .map(ConstantReader.new) 21 | .map((e) => constantReaderToSource(e, allocator)) 22 | .join(', '); 23 | final nameds = revivable.namedArguments.entries 24 | .map((e) => MapEntry(e.key, ConstantReader(e.value))) 25 | .map((e) => '${e.key}: ${constantReaderToSource(e.value, allocator)}') 26 | .join(', '); 27 | final positionText = positions.let((it) => it.isEmpty ? '' : '$it,'); 28 | final namedText = nameds.let((it) => it.isEmpty ? '' : '$it,'); 29 | return '$constructorRefer($positionText$namedText)'; 30 | } 31 | } 32 | 33 | extension on T { 34 | V let(V Function(T it) transform) => transform(this); 35 | } 36 | -------------------------------------------------------------------------------- /packages/playbook_generator/lib/src/playbook_builder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:analyzer/dart/element/element2.dart'; 4 | import 'package:build/build.dart'; 5 | import 'package:code_builder/code_builder.dart'; 6 | import 'package:dart_style/dart_style.dart'; 7 | import 'package:glob/glob.dart'; 8 | import 'package:path/path.dart'; 9 | import 'package:playbook_generator/src/constant_reader_utils.dart'; 10 | import 'package:source_gen/source_gen.dart' 11 | show LibraryReader, TypeChecker, defaultFileHeader; 12 | 13 | class PlaybookBuilder implements Builder { 14 | const PlaybookBuilder(this._config); 15 | 16 | static const _playbookUrl = 'package:playbook/playbook.dart'; 17 | static const _defaultInput = 'lib/**.dart'; 18 | static const _defaultOutput = 'generated_playbook.dart'; 19 | 20 | final Map _config; 21 | String get _input => _config['input'] as String? ?? _defaultInput; 22 | String get _output => _config['output'] as String? ?? _defaultOutput; 23 | 24 | @override 25 | Map> get buildExtensions => { 26 | r'$lib$': [_output], 27 | }; 28 | 29 | @override 30 | FutureOr build(BuildStep buildStep) async { 31 | final storyAssets = buildStep.findAssets(Glob(_input)); 32 | final storyFunctions = []; 33 | 34 | await for (final input in storyAssets) { 35 | final storyLibrary = await buildStep.resolver.libraryFor(input); 36 | final storyLibraryReader = LibraryReader(storyLibrary); 37 | 38 | final scenarioCodes = _createScenarioCodes(storyLibraryReader); 39 | if (scenarioCodes.isEmpty) continue; 40 | 41 | final storyTitle = _findStoryTitle(storyLibraryReader); 42 | storyFunctions.add( 43 | _createStoryFunction( 44 | storyTitle, 45 | scenarioCodes, 46 | storyLibraryReader, 47 | ), 48 | ); 49 | } 50 | 51 | final storiesLibrary = Library( 52 | (b) => b 53 | ..body.addAll([ 54 | _createPlaybookGetter(), 55 | _createStoriesGetter(storyFunctions), 56 | ...storyFunctions, 57 | ]), 58 | ); 59 | final emitter = DartEmitter( 60 | allocator: Allocator.simplePrefixing(), 61 | orderDirectives: true, 62 | useNullSafetySyntax: true, 63 | ); 64 | final content = DartFormatter( 65 | languageVersion: DartFormatter.latestLanguageVersion, 66 | ).format( 67 | ''' 68 | $defaultFileHeader 69 | 70 | ${storiesLibrary.accept(emitter)} 71 | ''', 72 | ); 73 | await buildStep.writeAsString(_outputAssetId(buildStep), content); 74 | } 75 | 76 | List _createScenarioCodes(LibraryReader storyLibraryReader) { 77 | final uri = storyLibraryReader.element.librarySource.uri.toString(); 78 | 79 | const generatedScenarioTypeChecker = TypeChecker.fromUrl( 80 | 'package:playbook/src/generate_scenario.dart#GenerateScenario', 81 | ); 82 | final generatedScenarioCodes = storyLibraryReader 83 | .annotatedWith(generatedScenarioTypeChecker) 84 | .where((e) { 85 | const w = 'Widget'; 86 | const bc = 'BuildContext'; 87 | 88 | final element = e.element; 89 | if (!element.isPublic) return false; 90 | if (element is ClassFragment) { 91 | final elm = (element as ClassFragment).element; 92 | return (elm.unnamedConstructor2?.isDefaultConstructor ?? false) && 93 | elm.allSupertypes.any( 94 | (s) => s.getDisplayString() == w, 95 | ); 96 | } else if (element is TopLevelFunctionFragment) { 97 | final elm = (element as TopLevelFunctionFragment).element; 98 | final parameters = elm.formalParameters; 99 | final firstParam = parameters.firstOrNull?.type.getDisplayString(); 100 | return parameters.length <= 1 && 101 | (firstParam?.contains(bc) ?? true) && 102 | elm.returnType.getDisplayString() == w; 103 | } else { 104 | return false; 105 | } 106 | }).map( 107 | (e) { 108 | final annotation = e.annotation; 109 | final element = e.element; 110 | final title = annotation.read('title'); 111 | final titleParam = title.isString 112 | ? title.stringValue 113 | : element.displayName.replaceFirst(r'$', '').replaceAll('_', ' '); 114 | return Code.scope((a) { 115 | final layout = constantReaderToSource(annotation.read('layout'), a); 116 | final builder = a(refer(element.displayName, uri)); 117 | final String scenarioName; 118 | final String childBuilder; 119 | 120 | if (element is TopLevelFunctionFragment && 121 | (element as TopLevelFunctionFragment) 122 | .formalParameters 123 | .isNotEmpty) { 124 | scenarioName = 'Scenario.builder'; 125 | childBuilder = 'builder: $builder'; 126 | } else { 127 | scenarioName = 'Scenario'; 128 | childBuilder = 'child: $builder()'; 129 | } 130 | return ''' 131 | ${a(refer(scenarioName, _playbookUrl))}( 132 | '$titleParam', 133 | layout: $layout, 134 | $childBuilder, 135 | )'''; 136 | }); 137 | }, 138 | ); 139 | 140 | final scenarioCodes = storyLibraryReader.element.topLevelElements 141 | .whereType() 142 | .map((e) => e.element) 143 | .where((e) => e.isPublic && e.formalParameters.isEmpty) 144 | .expand( 145 | (e) { 146 | final returnTypeString = e.returnType.getDisplayString(); 147 | final scenarioRefer = refer(e.displayName, uri); 148 | if (returnTypeString == 'Scenario') { 149 | return [scenarioRefer([]).code]; 150 | } else if (returnTypeString == 'List') { 151 | return [Code.scope((a) => '...${a(scenarioRefer)}()')]; 152 | } else { 153 | return []; 154 | } 155 | }, 156 | ); 157 | return [...generatedScenarioCodes, ...scenarioCodes]; 158 | } 159 | 160 | Method _createStoryFunction( 161 | String storyTitle, 162 | List scenarioCodes, 163 | LibraryReader storyLibraryReader, 164 | ) { 165 | final storyRefer = refer('Story', _playbookUrl); 166 | final name = storyLibraryReader.element.source.uri.pathSegments 167 | .skip(1) 168 | .map((e) => '\$${e.split('.').first}') 169 | .join(); 170 | final storyFunction = Method( 171 | (b) => b 172 | ..returns = storyRefer 173 | ..name = '_$name\$Story' 174 | ..body = storyRefer.call([ 175 | literalString(storyTitle), 176 | ], { 177 | 'scenarios': literalList(scenarioCodes), 178 | }).code, 179 | ); 180 | return storyFunction; 181 | } 182 | 183 | Method _createStoriesGetter(List storyMethods) { 184 | final bodyExpression = 185 | literalList(storyMethods.map((e) => refer('${e.name}()'))); 186 | return Method( 187 | (b) => b 188 | ..name = 'stories' 189 | ..type = MethodType.getter 190 | ..returns = TypeReference( 191 | (b) => b 192 | ..symbol = 'List' 193 | ..types.add(refer('Story', _playbookUrl).type), 194 | ) 195 | ..body = bodyExpression.code, 196 | ); 197 | } 198 | 199 | Method _createPlaybookGetter() { 200 | final playbookRefer = refer('Playbook', _playbookUrl); 201 | return Method( 202 | (b) => b 203 | ..name = 'playbook' 204 | ..type = MethodType.getter 205 | ..returns = playbookRefer 206 | ..body = playbookRefer.call([], { 207 | 'stories': refer('stories'), 208 | }).code, 209 | ); 210 | } 211 | 212 | String _findStoryTitle(LibraryReader storyLibraryReader) { 213 | late final fullName = storyLibraryReader.element.source.fullName; 214 | final storyTitle = storyLibraryReader.element.topLevelElements 215 | .whereType() 216 | .map((e) => e.element) 217 | .firstWhere( 218 | (e) => e.displayName == 'storyTitle', 219 | orElse: () => throw StateError( 220 | 'Library $fullName need define the story title.', 221 | ), 222 | ) 223 | .computeConstantValue() 224 | ?.toStringValue(); 225 | if (storyTitle == null) { 226 | throw StateError('Library $fullName storyTitle must be const.'); 227 | } 228 | return storyTitle; 229 | } 230 | 231 | AssetId _outputAssetId(BuildStep buildStep) { 232 | return AssetId( 233 | buildStep.inputId.package, 234 | join('lib', _output), 235 | ); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /packages/playbook_generator/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: playbook_generator 2 | description: Playbook Generator Package 3 | version: 1.1.0 4 | repository: https://github.com/playbook-ui/playbook-flutter 5 | documentation: https://github.com/playbook-ui/playbook-flutter 6 | issue_tracker: https://github.com/playbook-ui/playbook-flutter/issues 7 | 8 | environment: 9 | sdk: ">=3.6.0 <4.0.0" 10 | 11 | dependencies: 12 | analyzer: ^7.4.5 13 | build: ^2.4.1 14 | source_gen: ^2.0.0 15 | path: ^1.9.0 16 | glob: ^2.1.2 17 | code_builder: ^4.10.0 18 | dart_style: ^3.0.1 19 | 20 | dev_dependencies: 21 | very_good_analysis: ^7.0.0 22 | -------------------------------------------------------------------------------- /packages/playbook_snapshot/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.2 2 | 3 | - **FIX**: can use scenarioWidgetBuilder when snapshot #93 4 | 5 | ## 1.0.1 6 | 7 | - **FIX**: Device size for vertical layout fill. 8 | 9 | ## 1.0.0 10 | 11 | - **BREAKING CHANGE**: an instance of `SnapshotDevice` is passed when `playbook` is run. 12 | - **FEAT**: `SnapshotDevice` now supports `textScaler`, `pixelRatio` and `safeAreaInsets`. If you don't want the `SafeArea`, set `SafeAreaInsets()` to `safeAreaInsets`. 13 | - **FEAT**: `ScenarioWidget` shows `Checkered` background. If you don't want to show the background, set `null` to `checkeredColor`. 14 | - **FEAT**: can change whether to use `Material` widgets as a base when taking snapshots. 15 | - **FIX**: deprecate `directoryPath` and `snapshotDir`, use `snapshotDir` and `subDir` instead. 16 | 17 | ## 0.3.0+1 18 | 19 | - **CHORE**: fix playbook version. 20 | 21 | ## 0.3.0 22 | 23 | - **FEAT**: can load assets that are included in dependencies. 24 | - **BREAKING CHANGE**: upgrade sdk and flutter. 25 | 26 | ## 0.2.1 27 | 28 | - **FIX**: Support `Scrollables` have no `ScrollController`. 29 | 30 | ## 0.2.0 31 | 32 | - **FEAT**: Added support for defining fonts for `playbook_snapshot` in `pubspec.yaml`. 33 | 34 | ## 0.1.4 35 | 36 | - **FIX**: load multiple fonts ([#58](https://github.com/playbook-ui/playbook-flutter/issues/58)). 37 | 38 | ## 0.1.3 39 | 40 | - **BREAKING CHANGE**: SnapshotDevice change from enum to normal class. 41 | 42 | ## 0.1.2 43 | 44 | - **FIX**: find by the scenario widget to take snapshots. 45 | 46 | ## 0.1.1 47 | 48 | - **FIX**: reset app for each test. 49 | 50 | ## 0.1.0 51 | 52 | - **BREAKING CHANGE**: `TestTool` needs `WidgetTester` instance which is created on `testWidget`. And `testWidgets` only needs to be called once. 53 | 54 | ```dart 55 | Future main() async { 56 | testWidgets('Take snapshots', (tester) async { 57 | await Playbook( 58 | stories: [], 59 | ).run( 60 | Snapshot( 61 | directoryPath: 'screenshots', 62 | devices: [], 63 | ), 64 | tester, // Pass in the `WidgetTester` created by the `testWidget`. 65 | (widget) { 66 | return MaterialApp( 67 | home: Material(child: widget), 68 | ); 69 | }, 70 | ); 71 | 72 | // If you want to run `TestTool` multiple times, run it inside the` testWidgets` closure. 73 | }); 74 | } 75 | 76 | ``` 77 | 78 | ## 0.0.3 79 | 80 | - **FEAT**: can be stop resizing `Scrollable`. 81 | 82 | ## 0.0.2 83 | 84 | - **BUILD**: bump deps. 85 | 86 | ## 0.0.1 87 | 88 | - initial release. 89 | -------------------------------------------------------------------------------- /packages/playbook_snapshot/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /packages/playbook_snapshot/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /packages/playbook_snapshot/lib/playbook_snapshot.dart: -------------------------------------------------------------------------------- 1 | export 'src/snapshot.dart'; 2 | export 'src/snapshot_device.dart'; 3 | export 'src/test_tool.dart'; 4 | -------------------------------------------------------------------------------- /packages/playbook_snapshot/lib/src/font_builder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:flutter/services.dart'; 5 | import 'package:playbook_snapshot/src/pubspec_reader.dart'; 6 | 7 | class FontBuilder { 8 | static Future loadFonts() async { 9 | await _loadFontManifest(); 10 | await _loadFontFamily(); 11 | } 12 | 13 | static Future _loadFontAssets( 14 | List fonts, 15 | Future Function(String asset) loadData, 16 | ) async { 17 | for (final manifest in fonts) { 18 | final font = manifest as Map?; 19 | final fonts = font?['fonts'] as List? ?? []; 20 | final fontFamily = font?['family'] as String? ?? ''; 21 | 22 | final fontLoader = FontLoader(fontFamily); 23 | for (final font in fonts) { 24 | final asset = (font as Map)['asset'] as String; 25 | fontLoader.addFont(loadData(asset)); 26 | } 27 | await fontLoader.load(); 28 | } 29 | } 30 | 31 | static Future _loadFontManifest() async { 32 | final fonts = await rootBundle.loadStructuredData( 33 | 'FontManifest.json', 34 | (string) => Future.value(json.decode(string) as List), 35 | ); 36 | 37 | await _loadFontAssets(fonts, rootBundle.load); 38 | } 39 | 40 | static Future _loadFontFamily() async { 41 | final playbook = PubspecReader.read('playbook_snapshot'); 42 | final fonts = playbook?['fonts'] as List? ?? []; 43 | 44 | Future load(String asset) async { 45 | return File(asset).readAsBytesSync().buffer.asByteData(); 46 | } 47 | 48 | await _loadFontAssets(fonts, load); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/playbook_snapshot/lib/src/pubspec_reader.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:yaml/yaml.dart'; 5 | 6 | class PubspecReader { 7 | static Map? read(String key) { 8 | final yamlString = File('pubspec.yaml').readAsStringSync(); 9 | final yaml = loadYaml(yamlString); 10 | final pubspec = json.decode(json.encode(yaml)) as Map?; 11 | return pubspec?[key] as Map?; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/playbook_snapshot/lib/src/snapshot.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:playbook/playbook.dart'; 5 | import 'package:playbook_snapshot/src/font_builder.dart'; 6 | import 'package:playbook_snapshot/src/pubspec_reader.dart'; 7 | import 'package:playbook_snapshot/src/snapshot_device.dart'; 8 | import 'package:playbook_snapshot/src/snapshot_support.dart'; 9 | import 'package:playbook_snapshot/src/test_tool.dart'; 10 | 11 | class Snapshot implements TestTool { 12 | const Snapshot({ 13 | this.canvasColor = Colors.white, 14 | this.checkeredColor = Colors.black12, 15 | this.checkeredRectSize, 16 | this.useMaterial = true, 17 | this.snapshotDir, 18 | this.subDir, 19 | @Deprecated( 20 | '''Write a snapshot_dir in playbook_snapshot in pubspec.yaml or use snapshotDir instead.''', 21 | ) 22 | this.directoryPath, 23 | required this.devices, 24 | @Deprecated( 25 | '''Write a sub_dir in playbook_snapshot in pubspec.yaml or use subDir instead.''', 26 | ) 27 | this.subdirectoryPath, 28 | }); 29 | 30 | static const _snapshotDir = 'snapshots'; 31 | 32 | final Color? canvasColor; 33 | final Color? checkeredColor; 34 | final double? checkeredRectSize; 35 | final bool useMaterial; 36 | final String? snapshotDir; 37 | final String? subDir; 38 | final List devices; 39 | // ignore: deprecated_consistency 40 | final String? directoryPath; 41 | // ignore: deprecated_consistency 42 | final String? subdirectoryPath; 43 | 44 | @override 45 | Future run( 46 | Playbook playbook, 47 | WidgetTester tester, 48 | PlaybookBuilder builder, { 49 | ScenarioWidgetBuilder? scenarioWidgetBuilder, 50 | Future Function(WidgetTester tester)? setUpEachTest, 51 | }) async { 52 | await tester.runAsync(() async { 53 | await FontBuilder.loadFonts(); 54 | await tester.pumpAndSettle(); 55 | }); 56 | final stopwatch = Stopwatch()..start(); 57 | 58 | final spec = PubspecReader.read('playbook_snapshot'); 59 | final dirPath = snapshotDir ?? 60 | spec?['snapshot_dir'] as String? ?? 61 | directoryPath ?? 62 | _snapshotDir; 63 | final subDirPath = 64 | subDir ?? spec?['sub_dir'] as String? ?? subdirectoryPath; 65 | final subPath = subDirPath != null ? '/$subDirPath' : ''; 66 | 67 | for (final device in devices) { 68 | final ensuredDirectoryPath = '$dirPath/${device.name}$subPath'; 69 | debugDefaultTargetPlatformOverride = device.platform; 70 | 71 | for (final story in playbook.stories) { 72 | for (final scenario in story.scenarios) { 73 | tester 74 | .printToConsole('Snapshot for ${story.title} ${scenario.title}'); 75 | stopwatch.reset(); 76 | 77 | runApp(Container(key: UniqueKey())); 78 | final snapshotWidget = builder( 79 | ScenarioWidget( 80 | canvasColor: canvasColor, 81 | checkeredColor: checkeredColor, 82 | checkeredRectSize: checkeredRectSize, 83 | useMaterial: useMaterial, 84 | builder: scenarioWidgetBuilder, 85 | scenario: scenario, 86 | ), 87 | device, 88 | ); 89 | 90 | final target = Builder( 91 | builder: (context) { 92 | return MediaQuery( 93 | data: MediaQuery.of(context).copyWith( 94 | padding: device.safeAreaInsets, 95 | viewPadding: device.safeAreaInsets, 96 | devicePixelRatio: device.pixelRatio, 97 | textScaler: device.textScaler, 98 | ), 99 | child: snapshotWidget, 100 | ); 101 | }, 102 | ); 103 | await tester.runAsync(() async { 104 | await SnapshotSupport.startDevice(target, tester, device); 105 | await SnapshotSupport.resize( 106 | target, 107 | scenario, 108 | tester, 109 | device, 110 | ); 111 | await SnapshotSupport.precacheAssetImage(tester); 112 | 113 | await setUpEachTest?.call(tester); 114 | }); 115 | 116 | await expectLater( 117 | find.byWidget(target), 118 | matchesGoldenFile( 119 | '$ensuredDirectoryPath/${story.title}/${scenario.title}.png', 120 | ), 121 | ); 122 | tester.printToConsole( 123 | 'Snapshot finished in ${stopwatch.elapsedMilliseconds / 1000}s', 124 | ); 125 | } 126 | } 127 | } 128 | // To avoid EXCEPTION CAUGHT 129 | debugDefaultTargetPlatformOverride = null; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /packages/playbook_snapshot/lib/src/snapshot_device.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | class SafeAreaInsets { 4 | const SafeAreaInsets({ 5 | this.portrait = EdgeInsets.zero, 6 | this.landscape = EdgeInsets.zero, 7 | }); 8 | 9 | final EdgeInsets portrait; 10 | final EdgeInsets landscape; 11 | } 12 | 13 | enum SnapshotDeviceOrientation { 14 | portrait, 15 | landscape, 16 | } 17 | 18 | class SnapshotDevice { 19 | const SnapshotDevice({ 20 | required this.name, 21 | required Size size, 22 | SafeAreaInsets safeAreaInsets = const SafeAreaInsets(), 23 | this.textScaler, 24 | this.pixelRatio = 1, 25 | this.orientation = SnapshotDeviceOrientation.portrait, 26 | required this.platform, 27 | }) : _size = size, 28 | _safeAreaInsets = safeAreaInsets; 29 | 30 | final String name; 31 | final Size _size; 32 | final SafeAreaInsets _safeAreaInsets; 33 | final TextScaler? textScaler; 34 | final double pixelRatio; 35 | final SnapshotDeviceOrientation orientation; 36 | final TargetPlatform platform; 37 | 38 | Size get size { 39 | return switch (orientation) { 40 | SnapshotDeviceOrientation.portrait => _size, 41 | SnapshotDeviceOrientation.landscape => _size.flipped, 42 | }; 43 | } 44 | 45 | EdgeInsets get safeAreaInsets { 46 | return switch (orientation) { 47 | SnapshotDeviceOrientation.portrait => _safeAreaInsets.portrait, 48 | SnapshotDeviceOrientation.landscape => _safeAreaInsets.landscape, 49 | }; 50 | } 51 | 52 | SnapshotDevice call(SnapshotDeviceOrientation orientation) { 53 | return copyWith( 54 | orientation: orientation, 55 | ); 56 | } 57 | 58 | SnapshotDevice copyWith({ 59 | String? name, 60 | Size? size, 61 | SafeAreaInsets? safeAreaInsets, 62 | SnapshotDeviceOrientation? orientation, 63 | TargetPlatform? platform, 64 | }) { 65 | return SnapshotDevice( 66 | name: name ?? this.name, 67 | size: size ?? _size, 68 | safeAreaInsets: safeAreaInsets ?? _safeAreaInsets, 69 | orientation: orientation ?? this.orientation, 70 | platform: platform ?? this.platform, 71 | ); 72 | } 73 | 74 | @Deprecated('Use iPhoneSE1st instead.') 75 | static const iPhoneSE = SnapshotDevice( 76 | name: 'iPhoneSE', 77 | size: Size(320, 568), 78 | platform: TargetPlatform.iOS, 79 | ); 80 | 81 | static const iPhoneSE1st = SnapshotDevice( 82 | name: 'iPhoneSE1st', 83 | size: Size(320, 568), 84 | safeAreaInsets: SafeAreaInsets( 85 | portrait: EdgeInsets.fromLTRB(0, 20, 0, 0), 86 | landscape: EdgeInsets.fromLTRB(0, 20, 0, 0), 87 | ), 88 | platform: TargetPlatform.iOS, 89 | ); 90 | 91 | @Deprecated('Use iPhoneSE2nd instead.') 92 | static const iPhone8 = SnapshotDevice( 93 | name: 'iPhone8', 94 | size: Size(375, 667), 95 | platform: TargetPlatform.iOS, 96 | ); 97 | 98 | static const iPhoneSE2nd = SnapshotDevice( 99 | name: 'iPhoneSE2nd', 100 | size: Size(375, 667), 101 | safeAreaInsets: SafeAreaInsets( 102 | portrait: EdgeInsets.fromLTRB(0, 20, 0, 0), 103 | ), 104 | platform: TargetPlatform.iOS, 105 | ); 106 | 107 | static const iPhone11 = SnapshotDevice( 108 | name: 'iPhone11', 109 | size: Size(414, 896), 110 | safeAreaInsets: SafeAreaInsets( 111 | portrait: EdgeInsets.fromLTRB(0, 48, 0, 34), 112 | landscape: EdgeInsets.fromLTRB(48, 0, 48, 21), 113 | ), 114 | platform: TargetPlatform.iOS, 115 | ); 116 | 117 | static const iPhone15 = SnapshotDevice( 118 | name: 'iPhone15', 119 | size: Size(393, 852), 120 | safeAreaInsets: SafeAreaInsets( 121 | portrait: EdgeInsets.fromLTRB(0, 59, 0, 34), 122 | landscape: EdgeInsets.fromLTRB(59, 0, 59, 21), 123 | ), 124 | platform: TargetPlatform.iOS, 125 | ); 126 | 127 | static const pixel4 = SnapshotDevice( 128 | name: 'pixel4', 129 | size: Size(411, 869), 130 | safeAreaInsets: SafeAreaInsets( 131 | portrait: EdgeInsets.fromLTRB(0, 24, 0, 0), 132 | landscape: EdgeInsets.fromLTRB(0, 24, 0, 0), 133 | ), 134 | platform: TargetPlatform.android, 135 | ); 136 | 137 | static const pixel6 = SnapshotDevice( 138 | name: 'pixel6', 139 | size: Size(411, 914), 140 | safeAreaInsets: SafeAreaInsets( 141 | portrait: EdgeInsets.fromLTRB(0, 24, 0, 0), 142 | landscape: EdgeInsets.fromLTRB(0, 24, 0, 0), 143 | ), 144 | platform: TargetPlatform.android, 145 | ); 146 | 147 | static const xperia = SnapshotDevice( 148 | name: 'xperia', 149 | size: Size(360, 640), 150 | safeAreaInsets: SafeAreaInsets( 151 | portrait: EdgeInsets.fromLTRB(0, 24, 0, 0), 152 | landscape: EdgeInsets.fromLTRB(0, 24, 0, 0), 153 | ), 154 | platform: TargetPlatform.android, 155 | ); 156 | } 157 | -------------------------------------------------------------------------------- /packages/playbook_snapshot/lib/src/snapshot_support.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | import 'package:playbook/playbook.dart'; 6 | import 'package:playbook_snapshot/src/snapshot_device.dart'; 7 | 8 | class SnapshotSupport { 9 | static const _maxTryResizeCount = 10; 10 | static const _maxSnapshotSize = 50000; 11 | 12 | static Future startDevice( 13 | Widget target, 14 | WidgetTester tester, 15 | SnapshotDevice device, 16 | ) async { 17 | tester.view.devicePixelRatio = device.pixelRatio; 18 | await _setSnapshotSize(tester, device.size); 19 | await tester.pumpWidget(target); 20 | await tester.pumpAndSettle(); 21 | } 22 | 23 | static Future resize( 24 | Widget target, 25 | Scenario scenario, 26 | WidgetTester tester, 27 | SnapshotDevice device, 28 | ) async { 29 | if (!scenario.layout.needsResizing) { 30 | return; 31 | } 32 | 33 | final absoluteSize = Size( 34 | scenario.layout.absoluteWidth(device), 35 | scenario.layout.absoluteHeight(device), 36 | ); 37 | 38 | Size snapshotSize; 39 | if (scenario.layout.needsCompressedResizing) { 40 | // We use scrollController.maxScrollExtent to calculate the snapshot size. 41 | // However, maxScrollExtent may report incorrectly. 42 | // To solve this, we repeatedly calculate size and update size until 43 | // we can get a stable value. 44 | var lastExtendedSize = Size( 45 | scenario.layout.compressedResizingTarget.needResizingWidth 46 | ? device.size.width 47 | : absoluteSize.width, 48 | scenario.layout.compressedResizingTarget.needResizingHeight 49 | ? device.size.height 50 | : absoluteSize.height, 51 | ); 52 | var resize = 0; 53 | while (true) { 54 | final scrollables = 55 | find.byType(Scrollable).evaluate().map((e) => e.widget); 56 | if (scrollables.isEmpty) break; 57 | 58 | // To obtain the ScrollPosition, 59 | // search for ScrollableStates in the innermost widget of Scrollables, 60 | // regardless of whether Scrollables have a ScrollController or not. 61 | final scrollableStates = scrollables 62 | .map( 63 | (scrollable) => find 64 | .descendant( 65 | of: find.byWidget(scrollable), 66 | matching: find.byWidgetPredicate((widget) => true), 67 | ) 68 | .last 69 | .evaluate() 70 | .map(Scrollable.maybeOf) 71 | .firstWhere((element) => element != null, orElse: () => null), 72 | ) 73 | .where((element) => element != null); 74 | 75 | var extendedSize = device.size; 76 | for (final scrollableState in scrollableStates) { 77 | extendedSize = _extendScrollableSnapshotSize( 78 | scrollableState: scrollableState!, 79 | currentExtendedSize: extendedSize, 80 | originSize: lastExtendedSize, 81 | resizingTarget: scenario.layout.compressedResizingTarget, 82 | ); 83 | } 84 | if (extendedSize <= lastExtendedSize) break; 85 | lastExtendedSize = extendedSize; 86 | await _setSnapshotSize(tester, lastExtendedSize); 87 | resize++; 88 | if (resize >= _maxTryResizeCount) { 89 | throw StateError( 90 | // ignore: lines_longer_than_80_chars 91 | 'Try resizing too many times. Please try to set your scenario to have a fixed size.', 92 | ); 93 | } 94 | if (extendedSize.width >= _maxSnapshotSize || 95 | extendedSize.height >= _maxSnapshotSize) { 96 | throw StateError( 97 | // ignore: lines_longer_than_80_chars 98 | 'Try resizing too large size $extendedSize. Please try to set your scenario to have a fixed size.', 99 | ); 100 | } 101 | } 102 | snapshotSize = lastExtendedSize; 103 | } else { 104 | snapshotSize = absoluteSize; 105 | } 106 | await _setSnapshotSize(tester, snapshotSize); 107 | } 108 | 109 | // see: https://github.com/flutter/flutter/issues/38997 110 | static Future precacheAssetImage( 111 | WidgetTester tester, 112 | ) async { 113 | for (final element in find.byType(Image).evaluate()) { 114 | final widget = element.widget as Image; 115 | final image = widget.image; 116 | await precacheImage(image, element); 117 | await tester.pumpAndSettle(); 118 | } 119 | } 120 | 121 | static Future _setSnapshotSize(WidgetTester tester, Size size) async { 122 | await tester.binding.setSurfaceSize(size); 123 | tester.view.physicalSize = size; 124 | await tester.pumpAndSettle(); 125 | } 126 | 127 | static Size _extendScrollableSnapshotSize({ 128 | required ScrollableState scrollableState, 129 | required Size currentExtendedSize, 130 | required Size originSize, 131 | required _CompressedResizingTarget resizingTarget, 132 | }) { 133 | ScrollPosition? position; 134 | try { 135 | position = scrollableState.position; 136 | } catch (_) {} 137 | if (position == null) { 138 | return Size( 139 | resizingTarget.needResizingWidth 140 | ? max(currentExtendedSize.width, originSize.width) 141 | : originSize.width, 142 | resizingTarget.needResizingHeight 143 | ? max(currentExtendedSize.height, originSize.height) 144 | : originSize.height, 145 | ); 146 | } 147 | 148 | final scrollAxis = position.axis; 149 | final maxScrollExtent = position.maxScrollExtent; 150 | 151 | final Size newExtendedSize; 152 | switch (scrollAxis) { 153 | case Axis.horizontal: 154 | final height = max(originSize.height, currentExtendedSize.height); 155 | final width = 156 | max(maxScrollExtent + originSize.width, currentExtendedSize.width); 157 | newExtendedSize = Size(width, height); 158 | case Axis.vertical: 159 | final height = max( 160 | maxScrollExtent + originSize.height, 161 | currentExtendedSize.height, 162 | ); 163 | final width = max(originSize.width, currentExtendedSize.width); 164 | newExtendedSize = Size(width, height); 165 | } 166 | return Size( 167 | resizingTarget.needResizingWidth 168 | ? newExtendedSize.width 169 | : originSize.width, 170 | resizingTarget.needResizingHeight 171 | ? newExtendedSize.height 172 | : originSize.height, 173 | ); 174 | } 175 | } 176 | 177 | extension on ScenarioLayout { 178 | bool get needsResizing { 179 | return !(v is ScenarioLayoutFill && h is ScenarioLayoutFill); 180 | } 181 | 182 | bool get needsCompressedResizing { 183 | return v is ScenarioLayoutCompressed || h is ScenarioLayoutCompressed; 184 | } 185 | 186 | _CompressedResizingTarget get compressedResizingTarget { 187 | if (v is ScenarioLayoutCompressed && h is ScenarioLayoutCompressed) { 188 | return _CompressedResizingTarget.both; 189 | } else if (v is ScenarioLayoutCompressed) { 190 | return _CompressedResizingTarget.vertical; 191 | } else if (h is ScenarioLayoutCompressed) { 192 | return _CompressedResizingTarget.horizontal; 193 | } else { 194 | throw StateError('No need compressed resizing.'); 195 | } 196 | } 197 | 198 | double absoluteWidth(SnapshotDevice device) { 199 | switch (h.runtimeType) { 200 | case ScenarioLayoutFixed _: 201 | return (h as ScenarioLayoutFixed).value; 202 | case ScenarioLayoutFill _: 203 | return device.size.width; 204 | case ScenarioLayoutCompressed _: 205 | return 0; 206 | } 207 | return device.size.width; 208 | } 209 | 210 | double absoluteHeight(SnapshotDevice device) { 211 | switch (v.runtimeType) { 212 | case ScenarioLayoutFixed _: 213 | return (v as ScenarioLayoutFixed).value; 214 | case ScenarioLayoutFill _: 215 | return device.size.height; 216 | case ScenarioLayoutCompressed _: 217 | return 0; 218 | } 219 | return device.size.height; 220 | } 221 | } 222 | 223 | enum _CompressedResizingTarget { 224 | horizontal, 225 | vertical, 226 | both, 227 | } 228 | 229 | extension on _CompressedResizingTarget { 230 | bool get needResizingWidth => 231 | this == _CompressedResizingTarget.both || 232 | this == _CompressedResizingTarget.horizontal; 233 | 234 | bool get needResizingHeight => 235 | this == _CompressedResizingTarget.both || 236 | this == _CompressedResizingTarget.vertical; 237 | } 238 | -------------------------------------------------------------------------------- /packages/playbook_snapshot/lib/src/test_tool.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:playbook/playbook.dart'; 4 | import 'package:playbook_snapshot/src/snapshot_device.dart'; 5 | 6 | typedef PlaybookBuilder = Widget Function(Widget, SnapshotDevice); 7 | 8 | abstract class TestTool { 9 | Future run( 10 | Playbook playbook, 11 | WidgetTester tester, 12 | PlaybookBuilder builder, { 13 | ScenarioWidgetBuilder? scenarioWidgetBuilder, 14 | Future Function(WidgetTester tester)? setUpEachTest, 15 | }); 16 | } 17 | 18 | extension PlaybookExt on Playbook { 19 | Future run( 20 | TestTool test, 21 | WidgetTester tester, 22 | PlaybookBuilder builder, { 23 | ScenarioWidgetBuilder? scenarioWidgetBuilder, 24 | Future Function(WidgetTester tester)? setUpEachTest, 25 | }) async { 26 | await test.run( 27 | this, 28 | tester, 29 | builder, 30 | scenarioWidgetBuilder: scenarioWidgetBuilder, 31 | setUpEachTest: setUpEachTest, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/playbook_snapshot/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: playbook_snapshot 2 | description: Playbook Snapshot package. 3 | version: 1.0.2 4 | repository: https://github.com/playbook-ui/playbook-flutter 5 | documentation: https://github.com/playbook-ui/playbook-flutter 6 | issue_tracker: https://github.com/playbook-ui/playbook-flutter/issues 7 | 8 | environment: 9 | sdk: ">=3.6.0 <4.0.0" 10 | flutter: ">=3.27.0" 11 | 12 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | flutter_test: 16 | sdk: flutter 17 | 18 | playbook: ^1.0.0 19 | yaml: ^3.1.2 20 | 21 | dev_dependencies: 22 | very_good_analysis: ^7.0.0 23 | -------------------------------------------------------------------------------- /packages/playbook_snapshot/test/playbook_snapshot_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | test('adds one to input values', () {}); 5 | } 6 | -------------------------------------------------------------------------------- /packages/playbook_ui/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.1.0 2 | 3 | - **FEAT**: can set `ScenarioWidgetBuilder` to `ScenarioWidget`. 4 | 5 | ## 1.0.0 6 | 7 | - **FEAT**: `ScenarioWidget` shows `Checkered` background. If you don't want to show the background, set `checkeredColor` to `null`. 8 | 9 | ## 0.1.0+1 10 | 11 | - **CHORE**: fix playbook version. 12 | 13 | ## 0.1.0 14 | 15 | - **BREAKING CHANGE**: upgrade sdk and flutter. 16 | 17 | ## 0.0.6 18 | 19 | - **FIX**: Rename SearchBar to SearchBox ([#62](https://github.com/playbook-ui/playbook-flutter/issues/62)). 20 | 21 | ## 0.0.5 22 | 23 | - **FEAT**: PlaybookGallery now respects AppBarTheme#titleTextStyle ([#56](https://github.com/playbook-ui/playbook-flutter/issues/56)). ([f248d407](https://github.com/playbook-ui/playbook-flutter/commit/f248d407f37c3c95eedc546c29f3d88d8ef308fc)) 24 | 25 | ## 0.0.4+1 26 | 27 | - **FIX**: add listener to text controller ([#55](https://github.com/playbook-ui/playbook-flutter/issues/55)). ([7c0d4ece](https://github.com/playbook-ui/playbook-flutter/commit/7c0d4ece095752300ef2eaed3bca0c8d2df8144c)) 28 | 29 | ## 0.0.4 30 | 31 | - **FEAT**: set scenario thumbnail scale parameter from PlaybookGallery. 32 | - **FEAT**: expose searchTextController. 33 | 34 | ## 0.0.3+2 35 | 36 | - **FIX**: search bar initial state. 37 | 38 | ## 0.0.3+1 39 | 40 | - **FIX**: abandon scroll_to_index, set story to search text directly. 41 | 42 | ## 0.0.3 43 | 44 | - **FEAT**: update gallery ui. 45 | - **FEAT**: touch app bar to scroll to top. 46 | - **FEAT**: allow auto scroll to story from menu bar. 47 | - **FEAT**: allow set custom actions. 48 | - **FEAT**: disable hero mode instead of nested navigator hack. 49 | - **CHORE**: melos bootstrap. 50 | 51 | ## 0.0.2 52 | 53 | - **BUILD**: bump deps. 54 | 55 | ## [0.0.1] 56 | 57 | - initial release. 58 | -------------------------------------------------------------------------------- /packages/playbook_ui/LICENSE: -------------------------------------------------------------------------------- 1 | ../../LICENSE -------------------------------------------------------------------------------- /packages/playbook_ui/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /packages/playbook_ui/lib/playbook_ui.dart: -------------------------------------------------------------------------------- 1 | 2 | export 'src/playbook_gallery.dart'; 3 | -------------------------------------------------------------------------------- /packages/playbook_ui/lib/src/component/component.dart: -------------------------------------------------------------------------------- 1 | export 'dialog_scaffold.dart'; 2 | export 'scalable_button.dart'; 3 | export 'search_box.dart'; 4 | export 'story_drawer.dart'; 5 | -------------------------------------------------------------------------------- /packages/playbook_ui/lib/src/component/dialog_scaffold.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DialogScaffold extends StatelessWidget { 4 | const DialogScaffold({ 5 | super.key, 6 | this.body, 7 | this.title, 8 | }); 9 | 10 | final Widget? body; 11 | final Widget? title; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | const height = 44.0; 16 | 17 | return Scaffold( 18 | appBar: AppBar( 19 | automaticallyImplyLeading: false, 20 | toolbarHeight: height, 21 | centerTitle: true, 22 | title: title, 23 | actions: [ 24 | IconButton( 25 | onPressed: Navigator.of(context).pop, 26 | icon: const Icon(Icons.cancel), 27 | ), 28 | ], 29 | ), 30 | body: body, 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/playbook_ui/lib/src/component/scalable_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ScalableButton extends StatefulWidget { 4 | const ScalableButton({ 5 | super.key, 6 | required this.child, 7 | required this.onTap, 8 | }); 9 | 10 | final Widget child; 11 | final GestureTapCallback onTap; 12 | 13 | @override 14 | ScalableButtonState createState() => ScalableButtonState(); 15 | } 16 | 17 | class ScalableButtonState extends State 18 | with SingleTickerProviderStateMixin { 19 | double? _scale; 20 | AnimationController? _controller; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | 26 | _controller = AnimationController( 27 | vsync: this, 28 | duration: const Duration(milliseconds: 80), 29 | upperBound: 0.05, 30 | )..addListener(() => setState(() {})); 31 | } 32 | 33 | @override 34 | void dispose() { 35 | _controller?.dispose(); 36 | super.dispose(); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | _scale = 1.0 - (_controller?.value ?? 0); 42 | 43 | return GestureDetector( 44 | onTapDown: (_) => _controller?.forward(), 45 | onTapUp: (_) => _controller?.reverse(), 46 | onTapCancel: _controller?.reverse, 47 | onTap: widget.onTap, 48 | child: Transform.scale( 49 | scale: _scale ?? 1, 50 | child: widget.child, 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/playbook_ui/lib/src/component/search_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SearchBox extends StatefulWidget { 4 | const SearchBox({ 5 | super.key, 6 | required this.controller, 7 | }); 8 | 9 | final TextEditingController controller; 10 | 11 | @override 12 | State createState() => _SearchBoxState(); 13 | } 14 | 15 | class _SearchBoxState extends State { 16 | late bool _hasText = widget.controller.text.isNotEmpty; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | widget.controller.addListener(_onTextChanged); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | widget.controller.removeListener(_onTextChanged); 27 | super.dispose(); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | final iconColor = 33 | Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.48); 34 | return TextField( 35 | controller: widget.controller, 36 | keyboardType: TextInputType.text, 37 | decoration: InputDecoration( 38 | border: InputBorder.none, 39 | prefixIcon: Icon( 40 | Icons.search, 41 | size: 32, 42 | color: iconColor, 43 | ), 44 | suffixIcon: _hasText 45 | ? IconButton( 46 | onPressed: () => widget.controller.clear(), 47 | icon: Icon( 48 | Icons.cancel, 49 | size: 24, 50 | color: iconColor, 51 | ), 52 | ) 53 | : null, 54 | hintText: 'Search', 55 | ), 56 | ); 57 | } 58 | 59 | void _onTextChanged() { 60 | setState(() { 61 | _hasText = widget.controller.text.isNotEmpty; 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/playbook_ui/lib/src/component/story_drawer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:playbook/playbook.dart'; 3 | 4 | import 'package:playbook_ui/src/component/component.dart'; 5 | 6 | class StoryDrawer extends StatefulWidget { 7 | const StoryDrawer({ 8 | super.key, 9 | required this.stories, 10 | required this.textController, 11 | }); 12 | 13 | final List stories; 14 | final TextEditingController textController; 15 | 16 | @override 17 | StoryDrawerState createState() => StoryDrawerState(); 18 | } 19 | 20 | class StoryDrawerState extends State { 21 | final expandedIndex = {}; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Drawer( 26 | child: SafeArea( 27 | child: Column( 28 | children: [ 29 | Padding( 30 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), 31 | child: Material( 32 | shape: const StadiumBorder(), 33 | color: Theme.of(context) 34 | .colorScheme 35 | .onSurface 36 | .withValues(alpha: 0.06), 37 | clipBehavior: Clip.antiAlias, 38 | child: SearchBox( 39 | controller: widget.textController, 40 | ), 41 | ), 42 | ), 43 | const Divider(), 44 | Expanded( 45 | child: ListView.separated( 46 | separatorBuilder: (_, __) => const Divider(), 47 | itemBuilder: (BuildContext context, int index) { 48 | final story = widget.stories[index]; 49 | return Padding( 50 | padding: const EdgeInsets.symmetric(horizontal: 8), 51 | child: TextButton.icon( 52 | onPressed: () { 53 | widget.textController.text = story.title; 54 | Navigator.of(context).pop(); 55 | }, 56 | icon: const Icon(Icons.folder_outlined), 57 | label: SizedBox( 58 | width: double.infinity, 59 | child: Text( 60 | story.title, 61 | style: Theme.of(context) 62 | .textTheme 63 | .titleMedium 64 | ?.copyWith(fontWeight: FontWeight.bold), 65 | overflow: TextOverflow.ellipsis, 66 | ), 67 | ), 68 | ), 69 | ); 70 | }, 71 | itemCount: widget.stories.length, 72 | ), 73 | ), 74 | ], 75 | ), 76 | ), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/playbook_ui/lib/src/playbook_gallery.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:playbook/playbook.dart'; 3 | import 'package:playbook_ui/src/component/component.dart'; 4 | import 'package:playbook_ui/src/scenario_container.dart'; 5 | 6 | class PlaybookGallery extends StatefulWidget { 7 | const PlaybookGallery({ 8 | super.key, 9 | this.title = 'Playbook', 10 | this.canvasColor = Colors.white, 11 | this.checkeredColor = Colors.black12, 12 | this.scenarioThumbnailScale = 0.3, 13 | this.searchTextController, 14 | this.onCustomActionPressed, 15 | this.otherCustomActions = const [], 16 | required this.playbook, 17 | this.scenarioWidgetBuilder, 18 | }); 19 | 20 | final String title; 21 | final Color? canvasColor; 22 | final Color? checkeredColor; 23 | final double scenarioThumbnailScale; 24 | final TextEditingController? searchTextController; 25 | final VoidCallback? onCustomActionPressed; 26 | final List otherCustomActions; 27 | final Playbook playbook; 28 | final ScenarioWidgetBuilder? scenarioWidgetBuilder; 29 | 30 | @override 31 | PlaybookGalleryState createState() => PlaybookGalleryState(); 32 | } 33 | 34 | class PlaybookGalleryState extends State { 35 | final TextEditingController _defaultSearchTextController = 36 | TextEditingController(); 37 | TextEditingController get _effectiveSearchTextController => 38 | widget.searchTextController ?? _defaultSearchTextController; 39 | 40 | final _scrollController = ScrollController(); 41 | List _stories = []; 42 | 43 | @override 44 | void initState() { 45 | super.initState(); 46 | _defaultSearchTextController.addListener(_searchTextListener); 47 | widget.searchTextController?.addListener(_searchTextListener); 48 | _updateStoriesFromSearch(); 49 | } 50 | 51 | @override 52 | void dispose() { 53 | widget.searchTextController?.removeListener(_searchTextListener); 54 | _defaultSearchTextController.dispose(); 55 | super.dispose(); 56 | } 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | return Scaffold( 61 | appBar: AppBar( 62 | backgroundColor: Theme.of(context).colorScheme.inversePrimary, 63 | title: SearchBox( 64 | controller: _effectiveSearchTextController, 65 | ), 66 | actions: [ 67 | if (widget.onCustomActionPressed != null) 68 | IconButton( 69 | onPressed: widget.onCustomActionPressed, 70 | icon: const Icon(Icons.settings), 71 | ), 72 | ...widget.otherCustomActions, 73 | ], 74 | ), 75 | drawer: StoryDrawer( 76 | stories: _stories, 77 | textController: _effectiveSearchTextController, 78 | ), 79 | onDrawerChanged: (opened) { 80 | if (opened) _unfocus(); 81 | }, 82 | body: ListView.builder( 83 | controller: _scrollController, 84 | itemCount: _stories.length, 85 | itemBuilder: (context, index) { 86 | final story = _stories.elementAt(index); 87 | return Column( 88 | crossAxisAlignment: CrossAxisAlignment.start, 89 | children: [ 90 | const SizedBox(height: 16), 91 | Row( 92 | children: [ 93 | const SizedBox(width: 16), 94 | Icon( 95 | Icons.folder_outlined, 96 | size: 32, 97 | color: Theme.of(context).colorScheme.primary, 98 | ), 99 | const SizedBox(width: 8), 100 | Flexible( 101 | child: Text( 102 | story.title, 103 | style: Theme.of(context) 104 | .textTheme 105 | .titleLarge 106 | ?.copyWith(fontWeight: FontWeight.bold), 107 | ), 108 | ), 109 | const SizedBox(width: 16), 110 | ], 111 | ), 112 | const SizedBox(height: 16), 113 | SingleChildScrollView( 114 | key: PageStorageKey(index), 115 | padding: const EdgeInsets.symmetric(horizontal: 16), 116 | scrollDirection: Axis.horizontal, 117 | physics: const AlwaysScrollableScrollPhysics(), 118 | clipBehavior: Clip.none, 119 | child: Wrap( 120 | spacing: 16, 121 | children: story.scenarios 122 | .map( 123 | (e) => ScenarioContainer( 124 | key: ValueKey(e), 125 | scenario: e, 126 | thumbnailScale: widget.scenarioThumbnailScale, 127 | canvasColor: widget.canvasColor, 128 | checkeredColor: widget.checkeredColor, 129 | widgetBuilder: widget.scenarioWidgetBuilder, 130 | ), 131 | ) 132 | .toList() 133 | ..sort( 134 | (s1, s2) => 135 | s1.scenario.title.compareTo(s2.scenario.title), 136 | ), 137 | ), 138 | ), 139 | const SizedBox(height: 8), 140 | ], 141 | ); 142 | }, 143 | ), 144 | ); 145 | } 146 | 147 | @override 148 | void didUpdateWidget(covariant PlaybookGallery oldWidget) { 149 | super.didUpdateWidget(oldWidget); 150 | if (widget.searchTextController != oldWidget.searchTextController) { 151 | oldWidget.searchTextController?.removeListener(_searchTextListener); 152 | widget.searchTextController?.addListener(_searchTextListener); 153 | 154 | if (widget.searchTextController == null) { 155 | // Inherit the value from oldWidget 156 | _defaultSearchTextController.value = 157 | oldWidget.searchTextController?.value ?? TextEditingValue.empty; 158 | } 159 | } 160 | _updateStoriesFromSearch(); 161 | } 162 | 163 | void _searchTextListener() { 164 | setState(_updateStoriesFromSearch); 165 | } 166 | 167 | void _updateStoriesFromSearch() { 168 | if (_effectiveSearchTextController.text.isEmpty) { 169 | _stories = widget.playbook.stories.toList(); 170 | } else { 171 | final reg = RegExp( 172 | _effectiveSearchTextController.text, 173 | caseSensitive: false, 174 | ); 175 | _stories = widget.playbook.stories 176 | .map( 177 | (story) => Story( 178 | story.title, 179 | scenarios: story.title.contains(reg) 180 | ? story.scenarios 181 | : story.scenarios 182 | .where((scenario) => scenario.title.contains(reg)) 183 | .toList(), 184 | ), 185 | ) 186 | .where((story) => story.scenarios.isNotEmpty) 187 | .toList(); 188 | } 189 | _stories.sort((s1, s2) => s1.title.compareTo(s2.title)); 190 | } 191 | 192 | void _unfocus() { 193 | // see: https://github.com/flutter/flutter/issues/54277#issuecomment-640998757 194 | final currentScope = FocusScope.of(context); 195 | if (!currentScope.hasPrimaryFocus && currentScope.hasFocus) { 196 | FocusManager.instance.primaryFocus!.unfocus(); 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /packages/playbook_ui/lib/src/scenario_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:playbook/playbook.dart'; 3 | import 'package:playbook_ui/src/component/component.dart'; 4 | 5 | class ScenarioContainer extends StatelessWidget { 6 | const ScenarioContainer({ 7 | super.key, 8 | required this.scenario, 9 | required this.thumbnailScale, 10 | this.canvasColor, 11 | this.checkeredColor, 12 | this.widgetBuilder, 13 | }); 14 | 15 | final Scenario scenario; 16 | final double thumbnailScale; 17 | final Color? canvasColor; 18 | final Color? checkeredColor; 19 | final ScenarioWidgetBuilder? widgetBuilder; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | final size = MediaQuery.of(context).size; 24 | final thumbnailSize = size * thumbnailScale; 25 | return ScalableButton( 26 | child: Column( 27 | children: [ 28 | Card( 29 | elevation: 8, 30 | clipBehavior: Clip.antiAlias, 31 | margin: EdgeInsets.zero, 32 | child: SizedBox( 33 | width: thumbnailSize.width, 34 | height: thumbnailSize.height, 35 | child: Stack( 36 | children: [ 37 | Positioned( 38 | width: size.width, 39 | height: size.height, 40 | child: Focus( 41 | descendantsAreFocusable: false, 42 | child: IgnorePointer( 43 | child: Transform.scale( 44 | alignment: Alignment.topLeft, 45 | scale: thumbnailScale, 46 | child: Align( 47 | alignment: scenario.alignment, 48 | child: HeroMode( 49 | // Because we may have multiple heroes 50 | // that share the same tag 51 | enabled: false, 52 | child: ScenarioWidget( 53 | canvasColor: canvasColor, 54 | checkeredColor: checkeredColor, 55 | useMaterial: false, 56 | scenario: scenario, 57 | builder: widgetBuilder, 58 | ), 59 | ), 60 | ), 61 | ), 62 | ), 63 | ), 64 | ), 65 | ], 66 | ), 67 | ), 68 | ), 69 | const SizedBox(height: 16), 70 | Container( 71 | width: thumbnailSize.width, 72 | alignment: Alignment.center, 73 | child: Text( 74 | scenario.title, 75 | style: Theme.of(context).textTheme.titleMedium, 76 | ), 77 | ), 78 | const SizedBox(height: 16), 79 | ], 80 | ), 81 | onTap: () { 82 | FocusScope.of(context).unfocus(); 83 | Navigator.of(context).push( 84 | MaterialPageRoute( 85 | fullscreenDialog: true, 86 | builder: (context) { 87 | return DialogScaffold( 88 | title: Text(scenario.title), 89 | body: ScenarioWidget( 90 | canvasColor: canvasColor, 91 | checkeredColor: checkeredColor, 92 | useMaterial: false, 93 | scenario: scenario, 94 | builder: widgetBuilder, 95 | ), 96 | ); 97 | }, 98 | ), 99 | ); 100 | }, 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /packages/playbook_ui/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: playbook_ui 2 | description: Playbook UI package. 3 | version: 1.1.0 4 | repository: https://github.com/playbook-ui/playbook-flutter 5 | documentation: https://github.com/playbook-ui/playbook-flutter 6 | issue_tracker: https://github.com/playbook-ui/playbook-flutter/issues 7 | 8 | environment: 9 | sdk: ">=3.6.0 <4.0.0" 10 | flutter: ">=3.27.0" 11 | 12 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | 16 | playbook: ^1.1.0 17 | 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | very_good_analysis: ^7.0.0 22 | 23 | flutter: 24 | uses-material-design: true 25 | -------------------------------------------------------------------------------- /packages/playbook_ui/test/playbook_ui_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | test('adds one to input values', () {}); 5 | } 6 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: playbook 2 | description: playbook 3 | 4 | environment: 5 | sdk: ">=3.6.0 <4.0.0" 6 | 7 | dev_dependencies: 8 | melos: ^6.0.0 9 | very_good_analysis: ^7.0.0 10 | -------------------------------------------------------------------------------- /regconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": { 3 | "workingDir": ".reg", 4 | "actualDir": "examples/simple_example/simple_catalog_app/test/screenshots", 5 | "thresholdRate": 0, 6 | "addIgnore": true, 7 | "ximgdiff": { 8 | "invocationType": "client" 9 | } 10 | }, 11 | "plugins": { 12 | "reg-keygen-git-hash-plugin": true, 13 | "reg-notify-github-plugin": { 14 | "clientId": "MzY1NzQ2Njcx1y/ISaxMys/P1k3LKS0pSS3SNzQxtbQ0NTLST07UzSwqy8xLBQA=" 15 | }, 16 | "reg-publish-gcs-plugin": { 17 | "bucketName": "test-playbook-vrt" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | "group:allNonMajor", 6 | ":semanticCommits", 7 | ":semanticCommitTypeAll(chore)", 8 | ":semanticCommitScopeDisabled" 9 | ], 10 | "dependencyDashboard": false, 11 | "major": { 12 | "minimumReleaseAge": "7 days" 13 | }, 14 | "minor": { 15 | "minimumReleaseAge": "3 days" 16 | }, 17 | "patch": { 18 | "minimumReleaseAge": "2 days" 19 | }, 20 | "lockFileMaintenance": { 21 | "enabled": true 22 | }, 23 | "packageRules": [ 24 | { 25 | "matchUpdateTypes": [ 26 | "minor", 27 | "patch", 28 | "pin", 29 | "digest" 30 | ], 31 | "automerge": true 32 | }, 33 | { 34 | "groupName": "gradle", 35 | "matchManagers": [ 36 | "gradle", 37 | "gradle-wrapper" 38 | ], 39 | "enabled": false 40 | } 41 | ] 42 | } --------------------------------------------------------------------------------