├── .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 |
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 |
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 |
238 |
239 | #### [reg-viz/reg-suit](https://github.com/reg-viz/reg-suit)
240 |
241 |
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 |
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 | }
--------------------------------------------------------------------------------