├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── flutter-ci.yml ├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── codecov.yml ├── doc ├── img │ └── pal-showcase.jpg ├── pal-anchored.gif ├── pal-anchored.mp4 ├── pal-anchored2.gif └── pal-anchored2.mp4 ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── 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 │ └── main.dart ├── pubspec.lock └── pubspec.yaml ├── lib ├── anchored │ ├── anchor_model.dart │ ├── anchored_helper_widget.dart │ └── painters │ │ ├── anchor_painter.dart │ │ ├── anchored_circle_painter.dart │ │ ├── anchored_rect_painter.dart │ │ ├── animated_anchor.dart │ │ ├── animated_circle.dart │ │ └── animated_rect.dart ├── animations │ └── pop_anim.dart ├── helper_orchestrator.dart ├── pal_widgets.dart └── services │ ├── element_finder.dart │ └── overlay_helper.dart ├── pal-widgets.code-workspace ├── pubspec.lock ├── pubspec.yaml └── test ├── anchored ├── anchored_circle_widget_test.dart ├── anchored_rect_widget_test.dart └── widgets │ ├── anchored_circle_no_buttons.dart │ ├── anchored_circle_page_.dart │ ├── anchored_rect_no_buttons.dart │ └── anchored_rect_page.dart ├── screen_variants.dart └── services └── element_finder_test.dart /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Every request must be reviewed and accepted by: 2 | 3 | * @g-apparence -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Logs ** 24 | Run `flutter analyze` and attach any output of that command below. 25 | If there are any analysis errors, try resolving them before filing this issue. 26 | 27 | Paste the output of running `flutter doctor -v` here. 28 | 29 | **Additional context** 30 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/workflows/flutter-ci.yml: -------------------------------------------------------------------------------- 1 | name: pal-widgets 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: subosito/flutter-action@v1 11 | with: 12 | channel: 'stable' 13 | - name: Install Dependencies 14 | run: flutter pub get 15 | - name: Format 16 | run: dart format --set-exit-if-changed lib test 17 | - name: Analyze 18 | run: flutter analyze lib test 19 | - name: Run tests 20 | run: flutter test --no-pub --coverage --test-randomize-ordering-seed random 21 | - name: Check Code Coverage 22 | uses: ChicagoFlutter/lcov-cop@v1.0.0 23 | with: 24 | path: packages/alfreed/coverage/lcov.info 25 | - name: Upload coverage to Codecov 26 | uses: codecov/codecov-action@v1 27 | with: 28 | token: ${{ secrets.CODECOV_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/ephemeral 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | 71 | # Exceptions to above rules. 72 | !**/ios/**/default.mode1v3 73 | !**/ios/**/default.mode2v3 74 | !**/ios/**/default.pbxuser 75 | !**/ios/**/default.perspectivev3 76 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 18116933e77adc82f80866c928266a5b4f1ed645 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "example", 9 | "cwd": "example", 10 | "request": "launch", 11 | "type": "dart" 12 | }, 13 | { 14 | "name": "example (profile mode)", 15 | "cwd": "example", 16 | "request": "launch", 17 | "type": "dart", 18 | "flutterMode": "profile" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.2 2 | - update readme 3 | 4 | ## 0.2.0 5 | - AnchoredHelper has 2 factories to create the background animation : AnchoredCircleHoleHelper and AnchoredRectHoleHelper 6 | - negative button is now left button 7 | - right button is now right button 8 | 9 | ## 0.0.1 10 | - initial version 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Apparence.io 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | flutter anchored onboarding screen 3 |

4 |
5 |

6 | build 7 | 8 | License: MIT 9 | Pal widgets pubdev 10 |

11 |
12 | 13 | # Pal widgets 14 | A flutter package for better onboarding. 15 | 16 | A set of amazing onboarding widgets for your flutter applications. 17 | 18 | ## Install package 19 | add in your pubspec.yaml dependencies 20 | 21 | ```pal-widgets: 0.3.0``` 22 | 23 | ---- 24 | 25 | ## List of helpers 26 | 27 | | Name | description | status | 28 | |----------------|----------------------------------------------|-----------| 29 | | AnchoredHelper | An helper do describe another widget of your page. This creates an overlay above your page | :white_check_mark: | 30 | 31 | ## Anchored helper 32 | This widgets is highly inspired by google onboardings. This shows a circle around any widgets you want and add an overlay were you can add 33 | a message. 34 | 35 | First embbed you page or your app with a **HelperOrchestrator** widget. 36 | ```dart 37 | HelperOrchestrator( 38 | child: Scaffold( 39 | appBar: AppBar( 40 | title: Text(widget.title), 41 | ), 42 | body: ... 43 | ), 44 | ) 45 | ``` 46 | 47 | You now have to get a key *text1* for the widget you want to reference using 48 | ```dart 49 | Text( 50 | '$_counter', 51 | key: HelperOrchestrator.of(context).generateKey('text1'), 52 | ), 53 | ``` 54 | 55 | ### Example 56 | You can now show an anchored helper using 57 | ```dart 58 | final helper = AnchoredHelper( 59 | title: const Text('Title lorem pitume'), 60 | description: const Text('Lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum'),, 61 | bgColor: Colors.blue, 62 | leftBtnText: const Text('cancel'), 63 | rightBtnText: const Text('Ok, understood'), 64 | onError: () => print("widget not found"), 65 | positivBtnStyle: helperOutlineBtnStyle, 66 | negativeBtnStyle: helperOutlineBtnStyle, 67 | onLeftBtnTap: () => HelperOrchestrator.of(context).hideHelper(), 68 | onRightTap: () => HelperOrchestrator.of(context).hideHelper(), 69 | onTapAnchor: () => HelperOrchestrator.of(context).hideHelper(), 70 | ); 71 | 72 | // this will show an overlayed anchored widget using the key text1 73 | HelperOrchestrator.of(context).showAnchoredHelper('text1', helper); 74 | ``` 75 | 76 | ### properties 77 | 78 | | Property | description | Required | 79 | |--------------------|----------------------------------------------|-------------| 80 | | title | A Text widget to show as title | | 81 | | description | A Text widget to show as description | | 82 | | bgColor | A Color as Overlayed background | :white_check_mark: | 83 | | leftBtnText | A [Text] widget to show within the left button | | 84 | | rightBtnText | A [Text] widget to show within the right button | | 85 | | onLeftBtnTap | Action on tap left button | | 86 | | onRightTap | Action on tap right button | | 87 | | onError | Functions to call when widgets encounters any errors | | 88 | | leftBtnStyle | Material style to put on negativ button | | 89 | | rightBtnStyle | Material style to put on positiv button | | 90 | | onTapAnchor | Action to call when user tap on anchor | | 91 | | widgetFactory | The Widget factory to create the anchored helper. Default to AnchoredCircleHoleHelper.anchorFactory which creates a full overlay with an animated hole | | 92 | 93 | There is two Hole Anchor widget factories : 94 | - ```AnchoredCircleHoleHelper.anchorFactory```: creates a circle hole (default value) 95 | - ```AnchoredRectHoleHelper.anchorFactory``` : creates a rectangle hole on strict widget bounds 96 | 97 | ## Any question? 98 | Contact us on [our twitter](https://twitter.com/PalFlutter) 99 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "lib/anchored/painters/**" -------------------------------------------------------------------------------- /doc/img/pal-showcase.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/doc/img/pal-showcase.jpg -------------------------------------------------------------------------------- /doc/pal-anchored.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/doc/pal-anchored.gif -------------------------------------------------------------------------------- /doc/pal-anchored.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/doc/pal-anchored.mp4 -------------------------------------------------------------------------------- /doc/pal-anchored2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/doc/pal-anchored2.gif -------------------------------------------------------------------------------- /doc/pal-anchored2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/doc/pal-anchored2.mp4 -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "abb292a07e20d696c4568099f918f6c5f330e6b0" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 17 | base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 18 | - platform: android 19 | create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 20 | base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 21 | - platform: ios 22 | create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 23 | base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 24 | - platform: linux 25 | create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 26 | base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 27 | - platform: macos 28 | create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 29 | base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 30 | - platform: web 31 | create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 32 | base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 33 | - platform: windows 34 | create_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 35 | base_revision: abb292a07e20d696c4568099f918f6c5f330e6b0 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # pal_widgets_example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | 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.example" 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.example" 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 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip 6 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 97C146E61CF9000F007C117D /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = 97C146ED1CF9000F007C117D; 25 | remoteInfo = Runner; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXCopyFilesBuildPhase section */ 30 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 31 | isa = PBXCopyFilesBuildPhase; 32 | buildActionMask = 2147483647; 33 | dstPath = ""; 34 | dstSubfolderSpec = 10; 35 | files = ( 36 | ); 37 | name = "Embed Frameworks"; 38 | runOnlyForDeploymentPostprocessing = 0; 39 | }; 40 | /* End PBXCopyFilesBuildPhase section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 44 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 45 | 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 46 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 48 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 49 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 51 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 52 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 53 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 56 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 57 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 331C8082294A63A400263BE5 /* RunnerTests */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 331C807B294A618700263BE5 /* RunnerTests.swift */, 75 | ); 76 | path = RunnerTests; 77 | sourceTree = ""; 78 | }; 79 | 9740EEB11CF90186004384FC /* Flutter */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 83 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 84 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 85 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 86 | ); 87 | name = Flutter; 88 | sourceTree = ""; 89 | }; 90 | 97C146E51CF9000F007C117D = { 91 | isa = PBXGroup; 92 | children = ( 93 | 9740EEB11CF90186004384FC /* Flutter */, 94 | 97C146F01CF9000F007C117D /* Runner */, 95 | 97C146EF1CF9000F007C117D /* Products */, 96 | 331C8082294A63A400263BE5 /* RunnerTests */, 97 | ); 98 | sourceTree = ""; 99 | }; 100 | 97C146EF1CF9000F007C117D /* Products */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 97C146EE1CF9000F007C117D /* Runner.app */, 104 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 97C146F01CF9000F007C117D /* Runner */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 113 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 114 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 115 | 97C147021CF9000F007C117D /* Info.plist */, 116 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 117 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 118 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 119 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 120 | ); 121 | path = Runner; 122 | sourceTree = ""; 123 | }; 124 | /* End PBXGroup section */ 125 | 126 | /* Begin PBXNativeTarget section */ 127 | 331C8080294A63A400263BE5 /* RunnerTests */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; 130 | buildPhases = ( 131 | 331C807D294A63A400263BE5 /* Sources */, 132 | 331C807F294A63A400263BE5 /* Resources */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | 331C8086294A63A400263BE5 /* PBXTargetDependency */, 138 | ); 139 | name = RunnerTests; 140 | productName = RunnerTests; 141 | productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; 142 | productType = "com.apple.product-type.bundle.unit-test"; 143 | }; 144 | 97C146ED1CF9000F007C117D /* Runner */ = { 145 | isa = PBXNativeTarget; 146 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 147 | buildPhases = ( 148 | 9740EEB61CF901F6004384FC /* Run Script */, 149 | 97C146EA1CF9000F007C117D /* Sources */, 150 | 97C146EB1CF9000F007C117D /* Frameworks */, 151 | 97C146EC1CF9000F007C117D /* Resources */, 152 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 153 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | ); 159 | name = Runner; 160 | productName = Runner; 161 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 162 | productType = "com.apple.product-type.application"; 163 | }; 164 | /* End PBXNativeTarget section */ 165 | 166 | /* Begin PBXProject section */ 167 | 97C146E61CF9000F007C117D /* Project object */ = { 168 | isa = PBXProject; 169 | attributes = { 170 | BuildIndependentTargetsInParallel = YES; 171 | LastUpgradeCheck = 1510; 172 | ORGANIZATIONNAME = ""; 173 | TargetAttributes = { 174 | 331C8080294A63A400263BE5 = { 175 | CreatedOnToolsVersion = 14.0; 176 | TestTargetID = 97C146ED1CF9000F007C117D; 177 | }; 178 | 97C146ED1CF9000F007C117D = { 179 | CreatedOnToolsVersion = 7.3.1; 180 | LastSwiftMigration = 1100; 181 | }; 182 | }; 183 | }; 184 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 185 | compatibilityVersion = "Xcode 9.3"; 186 | developmentRegion = en; 187 | hasScannedForEncodings = 0; 188 | knownRegions = ( 189 | en, 190 | Base, 191 | ); 192 | mainGroup = 97C146E51CF9000F007C117D; 193 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 194 | projectDirPath = ""; 195 | projectRoot = ""; 196 | targets = ( 197 | 97C146ED1CF9000F007C117D /* Runner */, 198 | 331C8080294A63A400263BE5 /* RunnerTests */, 199 | ); 200 | }; 201 | /* End PBXProject section */ 202 | 203 | /* Begin PBXResourcesBuildPhase section */ 204 | 331C807F294A63A400263BE5 /* Resources */ = { 205 | isa = PBXResourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | 97C146EC1CF9000F007C117D /* Resources */ = { 212 | isa = PBXResourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 216 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 217 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 218 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXResourcesBuildPhase section */ 223 | 224 | /* Begin PBXShellScriptBuildPhase section */ 225 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 226 | isa = PBXShellScriptBuildPhase; 227 | alwaysOutOfDate = 1; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | ); 231 | inputPaths = ( 232 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 233 | ); 234 | name = "Thin Binary"; 235 | outputPaths = ( 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | shellPath = /bin/sh; 239 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 240 | }; 241 | 9740EEB61CF901F6004384FC /* Run Script */ = { 242 | isa = PBXShellScriptBuildPhase; 243 | alwaysOutOfDate = 1; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | ); 247 | inputPaths = ( 248 | ); 249 | name = "Run Script"; 250 | outputPaths = ( 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | shellPath = /bin/sh; 254 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 255 | }; 256 | /* End PBXShellScriptBuildPhase section */ 257 | 258 | /* Begin PBXSourcesBuildPhase section */ 259 | 331C807D294A63A400263BE5 /* Sources */ = { 260 | isa = PBXSourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | }; 267 | 97C146EA1CF9000F007C117D /* Sources */ = { 268 | isa = PBXSourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 272 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | /* End PBXSourcesBuildPhase section */ 277 | 278 | /* Begin PBXTargetDependency section */ 279 | 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { 280 | isa = PBXTargetDependency; 281 | target = 97C146ED1CF9000F007C117D /* Runner */; 282 | targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; 283 | }; 284 | /* End PBXTargetDependency section */ 285 | 286 | /* Begin PBXVariantGroup section */ 287 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 288 | isa = PBXVariantGroup; 289 | children = ( 290 | 97C146FB1CF9000F007C117D /* Base */, 291 | ); 292 | name = Main.storyboard; 293 | sourceTree = ""; 294 | }; 295 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 296 | isa = PBXVariantGroup; 297 | children = ( 298 | 97C147001CF9000F007C117D /* Base */, 299 | ); 300 | name = LaunchScreen.storyboard; 301 | sourceTree = ""; 302 | }; 303 | /* End PBXVariantGroup section */ 304 | 305 | /* Begin XCBuildConfiguration section */ 306 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ALWAYS_SEARCH_USER_PATHS = NO; 310 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 311 | CLANG_ANALYZER_NONNULL = YES; 312 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 313 | CLANG_CXX_LIBRARY = "libc++"; 314 | CLANG_ENABLE_MODULES = YES; 315 | CLANG_ENABLE_OBJC_ARC = YES; 316 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 317 | CLANG_WARN_BOOL_CONVERSION = YES; 318 | CLANG_WARN_COMMA = YES; 319 | CLANG_WARN_CONSTANT_CONVERSION = YES; 320 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 322 | CLANG_WARN_EMPTY_BODY = YES; 323 | CLANG_WARN_ENUM_CONVERSION = YES; 324 | CLANG_WARN_INFINITE_RECURSION = YES; 325 | CLANG_WARN_INT_CONVERSION = YES; 326 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 328 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 331 | CLANG_WARN_STRICT_PROTOTYPES = YES; 332 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 333 | CLANG_WARN_UNREACHABLE_CODE = YES; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 336 | COPY_PHASE_STRIP = NO; 337 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 338 | ENABLE_NS_ASSERTIONS = NO; 339 | ENABLE_STRICT_OBJC_MSGSEND = YES; 340 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 341 | GCC_C_LANGUAGE_STANDARD = gnu99; 342 | GCC_NO_COMMON_BLOCKS = YES; 343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 345 | GCC_WARN_UNDECLARED_SELECTOR = YES; 346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 347 | GCC_WARN_UNUSED_FUNCTION = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 350 | MTL_ENABLE_DEBUG_INFO = NO; 351 | SDKROOT = iphoneos; 352 | SUPPORTED_PLATFORMS = iphoneos; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | VALIDATE_PRODUCT = YES; 355 | }; 356 | name = Profile; 357 | }; 358 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 359 | isa = XCBuildConfiguration; 360 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 361 | buildSettings = { 362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 363 | CLANG_ENABLE_MODULES = YES; 364 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 365 | DEVELOPMENT_TEAM = 3P7AMLT6Q9; 366 | ENABLE_BITCODE = NO; 367 | INFOPLIST_FILE = Runner/Info.plist; 368 | LD_RUNPATH_SEARCH_PATHS = ( 369 | "$(inherited)", 370 | "@executable_path/Frameworks", 371 | ); 372 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 373 | PRODUCT_NAME = "$(TARGET_NAME)"; 374 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 375 | SWIFT_VERSION = 5.0; 376 | VERSIONING_SYSTEM = "apple-generic"; 377 | }; 378 | name = Profile; 379 | }; 380 | 331C8088294A63A400263BE5 /* Debug */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | BUNDLE_LOADER = "$(TEST_HOST)"; 384 | CODE_SIGN_STYLE = Automatic; 385 | CURRENT_PROJECT_VERSION = 1; 386 | GENERATE_INFOPLIST_FILE = YES; 387 | MARKETING_VERSION = 1.0; 388 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; 389 | PRODUCT_NAME = "$(TARGET_NAME)"; 390 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 391 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 392 | SWIFT_VERSION = 5.0; 393 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 394 | }; 395 | name = Debug; 396 | }; 397 | 331C8089294A63A400263BE5 /* Release */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | BUNDLE_LOADER = "$(TEST_HOST)"; 401 | CODE_SIGN_STYLE = Automatic; 402 | CURRENT_PROJECT_VERSION = 1; 403 | GENERATE_INFOPLIST_FILE = YES; 404 | MARKETING_VERSION = 1.0; 405 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | SWIFT_VERSION = 5.0; 408 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 409 | }; 410 | name = Release; 411 | }; 412 | 331C808A294A63A400263BE5 /* Profile */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | BUNDLE_LOADER = "$(TEST_HOST)"; 416 | CODE_SIGN_STYLE = Automatic; 417 | CURRENT_PROJECT_VERSION = 1; 418 | GENERATE_INFOPLIST_FILE = YES; 419 | MARKETING_VERSION = 1.0; 420 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests; 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | SWIFT_VERSION = 5.0; 423 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 424 | }; 425 | name = Profile; 426 | }; 427 | 97C147031CF9000F007C117D /* Debug */ = { 428 | isa = XCBuildConfiguration; 429 | buildSettings = { 430 | ALWAYS_SEARCH_USER_PATHS = NO; 431 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 432 | CLANG_ANALYZER_NONNULL = YES; 433 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 434 | CLANG_CXX_LIBRARY = "libc++"; 435 | CLANG_ENABLE_MODULES = YES; 436 | CLANG_ENABLE_OBJC_ARC = YES; 437 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 438 | CLANG_WARN_BOOL_CONVERSION = YES; 439 | CLANG_WARN_COMMA = YES; 440 | CLANG_WARN_CONSTANT_CONVERSION = YES; 441 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 442 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 443 | CLANG_WARN_EMPTY_BODY = YES; 444 | CLANG_WARN_ENUM_CONVERSION = YES; 445 | CLANG_WARN_INFINITE_RECURSION = YES; 446 | CLANG_WARN_INT_CONVERSION = YES; 447 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 448 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 449 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 450 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 451 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 452 | CLANG_WARN_STRICT_PROTOTYPES = YES; 453 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 454 | CLANG_WARN_UNREACHABLE_CODE = YES; 455 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 456 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 457 | COPY_PHASE_STRIP = NO; 458 | DEBUG_INFORMATION_FORMAT = dwarf; 459 | ENABLE_STRICT_OBJC_MSGSEND = YES; 460 | ENABLE_TESTABILITY = YES; 461 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 462 | GCC_C_LANGUAGE_STANDARD = gnu99; 463 | GCC_DYNAMIC_NO_PIC = NO; 464 | GCC_NO_COMMON_BLOCKS = YES; 465 | GCC_OPTIMIZATION_LEVEL = 0; 466 | GCC_PREPROCESSOR_DEFINITIONS = ( 467 | "DEBUG=1", 468 | "$(inherited)", 469 | ); 470 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 471 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 472 | GCC_WARN_UNDECLARED_SELECTOR = YES; 473 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 474 | GCC_WARN_UNUSED_FUNCTION = YES; 475 | GCC_WARN_UNUSED_VARIABLE = YES; 476 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 477 | MTL_ENABLE_DEBUG_INFO = YES; 478 | ONLY_ACTIVE_ARCH = YES; 479 | SDKROOT = iphoneos; 480 | TARGETED_DEVICE_FAMILY = "1,2"; 481 | }; 482 | name = Debug; 483 | }; 484 | 97C147041CF9000F007C117D /* Release */ = { 485 | isa = XCBuildConfiguration; 486 | buildSettings = { 487 | ALWAYS_SEARCH_USER_PATHS = NO; 488 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 489 | CLANG_ANALYZER_NONNULL = YES; 490 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 491 | CLANG_CXX_LIBRARY = "libc++"; 492 | CLANG_ENABLE_MODULES = YES; 493 | CLANG_ENABLE_OBJC_ARC = YES; 494 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 495 | CLANG_WARN_BOOL_CONVERSION = YES; 496 | CLANG_WARN_COMMA = YES; 497 | CLANG_WARN_CONSTANT_CONVERSION = YES; 498 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 499 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 500 | CLANG_WARN_EMPTY_BODY = YES; 501 | CLANG_WARN_ENUM_CONVERSION = YES; 502 | CLANG_WARN_INFINITE_RECURSION = YES; 503 | CLANG_WARN_INT_CONVERSION = YES; 504 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 505 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 506 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 507 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 508 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 509 | CLANG_WARN_STRICT_PROTOTYPES = YES; 510 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 511 | CLANG_WARN_UNREACHABLE_CODE = YES; 512 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 513 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 514 | COPY_PHASE_STRIP = NO; 515 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 516 | ENABLE_NS_ASSERTIONS = NO; 517 | ENABLE_STRICT_OBJC_MSGSEND = YES; 518 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 519 | GCC_C_LANGUAGE_STANDARD = gnu99; 520 | GCC_NO_COMMON_BLOCKS = YES; 521 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 522 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 523 | GCC_WARN_UNDECLARED_SELECTOR = YES; 524 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 525 | GCC_WARN_UNUSED_FUNCTION = YES; 526 | GCC_WARN_UNUSED_VARIABLE = YES; 527 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 528 | MTL_ENABLE_DEBUG_INFO = NO; 529 | SDKROOT = iphoneos; 530 | SUPPORTED_PLATFORMS = iphoneos; 531 | SWIFT_COMPILATION_MODE = wholemodule; 532 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 533 | TARGETED_DEVICE_FAMILY = "1,2"; 534 | VALIDATE_PRODUCT = YES; 535 | }; 536 | name = Release; 537 | }; 538 | 97C147061CF9000F007C117D /* Debug */ = { 539 | isa = XCBuildConfiguration; 540 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 541 | buildSettings = { 542 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 543 | CLANG_ENABLE_MODULES = YES; 544 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 545 | DEVELOPMENT_TEAM = 3P7AMLT6Q9; 546 | ENABLE_BITCODE = NO; 547 | INFOPLIST_FILE = Runner/Info.plist; 548 | LD_RUNPATH_SEARCH_PATHS = ( 549 | "$(inherited)", 550 | "@executable_path/Frameworks", 551 | ); 552 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 553 | PRODUCT_NAME = "$(TARGET_NAME)"; 554 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 555 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 556 | SWIFT_VERSION = 5.0; 557 | VERSIONING_SYSTEM = "apple-generic"; 558 | }; 559 | name = Debug; 560 | }; 561 | 97C147071CF9000F007C117D /* Release */ = { 562 | isa = XCBuildConfiguration; 563 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 564 | buildSettings = { 565 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 566 | CLANG_ENABLE_MODULES = YES; 567 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 568 | DEVELOPMENT_TEAM = 3P7AMLT6Q9; 569 | ENABLE_BITCODE = NO; 570 | INFOPLIST_FILE = Runner/Info.plist; 571 | LD_RUNPATH_SEARCH_PATHS = ( 572 | "$(inherited)", 573 | "@executable_path/Frameworks", 574 | ); 575 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 576 | PRODUCT_NAME = "$(TARGET_NAME)"; 577 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 578 | SWIFT_VERSION = 5.0; 579 | VERSIONING_SYSTEM = "apple-generic"; 580 | }; 581 | name = Release; 582 | }; 583 | /* End XCBuildConfiguration section */ 584 | 585 | /* Begin XCConfigurationList section */ 586 | 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { 587 | isa = XCConfigurationList; 588 | buildConfigurations = ( 589 | 331C8088294A63A400263BE5 /* Debug */, 590 | 331C8089294A63A400263BE5 /* Release */, 591 | 331C808A294A63A400263BE5 /* Profile */, 592 | ); 593 | defaultConfigurationIsVisible = 0; 594 | defaultConfigurationName = Release; 595 | }; 596 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 597 | isa = XCConfigurationList; 598 | buildConfigurations = ( 599 | 97C147031CF9000F007C117D /* Debug */, 600 | 97C147041CF9000F007C117D /* Release */, 601 | 249021D3217E4FDB00AE95B9 /* Profile */, 602 | ); 603 | defaultConfigurationIsVisible = 0; 604 | defaultConfigurationName = Release; 605 | }; 606 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 607 | isa = XCConfigurationList; 608 | buildConfigurations = ( 609 | 97C147061CF9000F007C117D /* Debug */, 610 | 97C147071CF9000F007C117D /* Release */, 611 | 249021D4217E4FDB00AE95B9 /* Profile */, 612 | ); 613 | defaultConfigurationIsVisible = 0; 614 | defaultConfigurationName = Release; 615 | }; 616 | /* End XCConfigurationList section */ 617 | }; 618 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 619 | } 620 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | 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 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apparence-io/pal-widgets/a9aba9be1dad29c09ab88ae1100302db36cca64b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:pal_widgets/pal_widgets.dart'; 3 | 4 | void main() { 5 | runApp(const MyApp()); 6 | } 7 | 8 | class MyApp extends StatelessWidget { 9 | const MyApp({Key? key}) : super(key: key); 10 | 11 | // This widget is the root of your application. 12 | @override 13 | Widget build(BuildContext context) { 14 | return MaterialApp( 15 | title: 'Flutter Demo', 16 | theme: ThemeData( 17 | primarySwatch: Colors.blue, 18 | splashFactory: InkSplash.splashFactory, 19 | ), 20 | home: const HelperOrchestrator( 21 | child: MyHomePage(title: 'Flutter Demo Home Page'), 22 | ), 23 | ); 24 | } 25 | } 26 | 27 | class MyHomePage extends StatefulWidget { 28 | const MyHomePage({Key? key, required this.title}) : super(key: key); 29 | 30 | final String title; 31 | 32 | @override 33 | State createState() => _MyHomePageState(); 34 | } 35 | 36 | class _MyHomePageState extends State { 37 | int _counter = 0; 38 | 39 | void _incrementCounter() { 40 | setState(() { 41 | _counter++; 42 | }); 43 | } 44 | 45 | @override 46 | void initState() { 47 | super.initState(); 48 | Future.delayed(const Duration(milliseconds: 500), () { 49 | HelperOrchestrator.of(context).showAnchoredHelper('btn', helper); 50 | }); 51 | } 52 | 53 | ButtonStyle get helperOutlineBtnStyle => OutlinedButton.styleFrom( 54 | padding: const EdgeInsets.all(12), 55 | textStyle: const TextStyle( 56 | color: Colors.white, 57 | fontSize: 18, 58 | ), 59 | side: const BorderSide( 60 | width: 1.0, 61 | style: BorderStyle.solid, 62 | color: Colors.white, 63 | ), 64 | shape: RoundedRectangleBorder( 65 | borderRadius: BorderRadius.circular(8.0), 66 | ), 67 | ); 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | return Scaffold( 72 | appBar: AppBar( 73 | title: Text(widget.title), 74 | ), 75 | body: Center( 76 | child: Column( 77 | mainAxisAlignment: MainAxisAlignment.center, 78 | children: [ 79 | const Text( 80 | 'You have pushed the button this many times:', 81 | ), 82 | Text( 83 | '$_counter', 84 | style: const TextStyle(color: Colors.blue, fontSize: 32), 85 | ), 86 | Text( 87 | 'test widget helper', 88 | key: HelperOrchestrator.of(context).generateKey('text2'), 89 | ), 90 | const SizedBox(height: 21), 91 | OutlinedButton( 92 | onPressed: () { 93 | HelperOrchestrator.of(context) 94 | .showAnchoredHelper('btn', helper); 95 | }, 96 | child: const Text('push me 2'), 97 | key: HelperOrchestrator.of(context).generateKey('btn'), 98 | ), 99 | ], 100 | ), 101 | ), 102 | floatingActionButton: FloatingActionButton( 103 | // key: HelperOrchestrator.of(context).generateKey('btn'), 104 | onPressed: _incrementCounter, 105 | tooltip: 'Increment', 106 | child: const Icon(Icons.add), 107 | ), 108 | ); 109 | } 110 | 111 | AnchoredHelper get helper => AnchoredHelper( 112 | title: const Text( 113 | 'Push to increment', 114 | textAlign: TextAlign.center, 115 | style: TextStyle( 116 | color: Colors.white, 117 | fontSize: 32, 118 | ), 119 | ), 120 | description: const Text( 121 | 'Tap on this button to increment the flutter demo counter', 122 | textAlign: TextAlign.center, 123 | style: TextStyle( 124 | color: Colors.white, 125 | fontSize: 21, 126 | ), 127 | ), 128 | bgColor: Colors.blue, 129 | // leftBtnText: const Text('cancel'), 130 | // rightBtnText: const Text('Ok, understood'), 131 | onError: () { 132 | // do as you want if error happens 133 | }, 134 | rightBtnStyle: helperOutlineBtnStyle, 135 | leftBtnStyle: helperOutlineBtnStyle, 136 | // onRightTap: () => HelperOrchestrator.of(context).hideHelper(), 137 | // onLeftBtnTap: () => HelperOrchestrator.of(context).hideHelper(), 138 | onTapAnchor: () => HelperOrchestrator.of(context).hideHelper(), 139 | widgetFactory: AnchoredCircleHoleHelper.anchorFactory, 140 | ); 141 | } 142 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.18.0" 44 | cupertino_icons: 45 | dependency: "direct main" 46 | description: 47 | name: cupertino_icons 48 | sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.0.8" 52 | fake_async: 53 | dependency: transitive 54 | description: 55 | name: fake_async 56 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "1.3.1" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_lints: 66 | dependency: "direct dev" 67 | description: 68 | name: flutter_lints 69 | sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493 70 | url: "https://pub.dev" 71 | source: hosted 72 | version: "1.0.4" 73 | flutter_test: 74 | dependency: "direct dev" 75 | description: flutter 76 | source: sdk 77 | version: "0.0.0" 78 | leak_tracker: 79 | dependency: transitive 80 | description: 81 | name: leak_tracker 82 | sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" 83 | url: "https://pub.dev" 84 | source: hosted 85 | version: "10.0.4" 86 | leak_tracker_flutter_testing: 87 | dependency: transitive 88 | description: 89 | name: leak_tracker_flutter_testing 90 | sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "3.0.3" 94 | leak_tracker_testing: 95 | dependency: transitive 96 | description: 97 | name: leak_tracker_testing 98 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 99 | url: "https://pub.dev" 100 | source: hosted 101 | version: "3.0.1" 102 | lints: 103 | dependency: transitive 104 | description: 105 | name: lints 106 | sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c 107 | url: "https://pub.dev" 108 | source: hosted 109 | version: "1.0.1" 110 | matcher: 111 | dependency: transitive 112 | description: 113 | name: matcher 114 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 115 | url: "https://pub.dev" 116 | source: hosted 117 | version: "0.12.16+1" 118 | material_color_utilities: 119 | dependency: transitive 120 | description: 121 | name: material_color_utilities 122 | sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" 123 | url: "https://pub.dev" 124 | source: hosted 125 | version: "0.8.0" 126 | meta: 127 | dependency: transitive 128 | description: 129 | name: meta 130 | sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" 131 | url: "https://pub.dev" 132 | source: hosted 133 | version: "1.12.0" 134 | pal_widgets: 135 | dependency: "direct main" 136 | description: 137 | path: ".." 138 | relative: true 139 | source: path 140 | version: "0.2.0" 141 | path: 142 | dependency: transitive 143 | description: 144 | name: path 145 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 146 | url: "https://pub.dev" 147 | source: hosted 148 | version: "1.9.0" 149 | sky_engine: 150 | dependency: transitive 151 | description: flutter 152 | source: sdk 153 | version: "0.0.99" 154 | source_span: 155 | dependency: transitive 156 | description: 157 | name: source_span 158 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 159 | url: "https://pub.dev" 160 | source: hosted 161 | version: "1.10.0" 162 | stack_trace: 163 | dependency: transitive 164 | description: 165 | name: stack_trace 166 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 167 | url: "https://pub.dev" 168 | source: hosted 169 | version: "1.11.1" 170 | stream_channel: 171 | dependency: transitive 172 | description: 173 | name: stream_channel 174 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 175 | url: "https://pub.dev" 176 | source: hosted 177 | version: "2.1.2" 178 | string_scanner: 179 | dependency: transitive 180 | description: 181 | name: string_scanner 182 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 183 | url: "https://pub.dev" 184 | source: hosted 185 | version: "1.2.0" 186 | term_glyph: 187 | dependency: transitive 188 | description: 189 | name: term_glyph 190 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 191 | url: "https://pub.dev" 192 | source: hosted 193 | version: "1.2.1" 194 | test_api: 195 | dependency: transitive 196 | description: 197 | name: test_api 198 | sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" 199 | url: "https://pub.dev" 200 | source: hosted 201 | version: "0.7.0" 202 | vector_math: 203 | dependency: transitive 204 | description: 205 | name: vector_math 206 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 207 | url: "https://pub.dev" 208 | source: hosted 209 | version: "2.1.4" 210 | vm_service: 211 | dependency: transitive 212 | description: 213 | name: vm_service 214 | sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" 215 | url: "https://pub.dev" 216 | source: hosted 217 | version: "14.2.1" 218 | sdks: 219 | dart: ">=3.3.0 <4.0.0" 220 | flutter: ">=3.18.0-18.0.pre.54" 221 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: pal_widgets_example 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.12.0 <3.0.0" 22 | 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | pal_widgets: 33 | path: ../ 34 | 35 | 36 | # The following adds the Cupertino Icons font to your application. 37 | # Use with the CupertinoIcons class for iOS style icons. 38 | cupertino_icons: ^1.0.2 39 | 40 | dev_dependencies: 41 | flutter_test: 42 | sdk: flutter 43 | 44 | # The "flutter_lints" package below contains a set of recommended lints to 45 | # encourage good coding practices. The lint set provided by the package is 46 | # activated in the `analysis_options.yaml` file located at the root of your 47 | # package. See that file for information about deactivating specific lint 48 | # rules and activating additional ones. 49 | flutter_lints: ^1.0.0 50 | 51 | # For information on the generic Dart part of this file, see the 52 | # following page: https://dart.dev/tools/pub/pubspec 53 | 54 | # The following section is specific to Flutter. 55 | flutter: 56 | 57 | # The following line ensures that the Material Icons font is 58 | # included with your application, so that you can use the icons in 59 | # the material Icons class. 60 | uses-material-design: true 61 | 62 | # To add assets to your application, add an assets section, like this: 63 | # assets: 64 | # - images/a_dot_burr.jpeg 65 | # - images/a_dot_ham.jpeg 66 | 67 | # An image asset can refer to one or more resolution-specific "variants", see 68 | # https://flutter.dev/assets-and-images/#resolution-aware. 69 | 70 | # For details regarding adding assets from package dependencies, see 71 | # https://flutter.dev/assets-and-images/#from-packages 72 | 73 | # To add custom fonts to your application, add a fonts section here, 74 | # in this "flutter" section. Each entry in this list should have a 75 | # "family" key with the font family name, and a "fonts" key with a 76 | # list giving the asset and other descriptors for the font. For 77 | # example: 78 | # fonts: 79 | # - family: Schyler 80 | # fonts: 81 | # - asset: fonts/Schyler-Regular.ttf 82 | # - asset: fonts/Schyler-Italic.ttf 83 | # style: italic 84 | # - family: Trajan Pro 85 | # fonts: 86 | # - asset: fonts/TrajanPro.ttf 87 | # - asset: fonts/TrajanPro_Bold.ttf 88 | # weight: 700 89 | # 90 | # For details regarding fonts from package dependencies, 91 | # see https://flutter.dev/custom-fonts/#from-packages 92 | -------------------------------------------------------------------------------- /lib/anchored/anchor_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | class Anchor { 4 | final Size size; 5 | final Offset offset; 6 | final Rect rect; 7 | 8 | const Anchor({ 9 | required this.size, 10 | required this.offset, 11 | required this.rect, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /lib/anchored/anchored_helper_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:pal_widgets/animations/pop_anim.dart'; 4 | 5 | import 'anchor_model.dart'; 6 | import 'painters/anchor_painter.dart'; 7 | import 'painters/animated_circle.dart'; 8 | 9 | class AnchorHelperWrapper extends InheritedWidget { 10 | final Anchor anchor; 11 | 12 | const AnchorHelperWrapper({ 13 | Key? key, 14 | required this.anchor, 15 | required Widget child, 16 | }) : super(key: key, child: child); 17 | 18 | static AnchorHelperWrapper? of(BuildContext context) => 19 | context.dependOnInheritedWidgetOfExactType(); 20 | 21 | @override 22 | bool updateShouldNotify(covariant InheritedWidget oldWidget) => true; 23 | } 24 | 25 | /// This widget is an helper based on the position of a widget 26 | /// you must have an ancestor of type [HelperOrchestrator] 27 | /// 28 | /// to create a 29 | /// You can change [widgetFactory] to create your own anchored widget or use one of 30 | /// - [AnchoredHoleHelper.anchorFactory] 31 | /// 32 | /// You are free to add a positiv button / negativ button or an onTap function 33 | /// on the anchor. 34 | class AnchoredHelper extends StatefulWidget { 35 | /// A [Text] widget to show as title 36 | final Text? title; 37 | 38 | /// A [Text] widget to show as description 39 | final Text? description; 40 | 41 | /// A Color as Overlayed background 42 | final Color bgColor; 43 | 44 | /// A [Text] widget to show within the left button 45 | final Text? leftBtnText; 46 | 47 | /// A [Text] widget to show within the right button 48 | final Text? rightBtnText; 49 | 50 | /// Functions to call when user tap on left or right button 51 | final Function? onLeftBtnTap, onRightTap, onTapBackground; 52 | 53 | /// Functions to call when widgets encounters any errors 54 | final Function? onError; 55 | 56 | /// Buttons material style 57 | final ButtonStyle? leftBtnStyle, rightBtnStyle; 58 | 59 | /// If you want to use a custom position. Else we will use the [HelperOrchestrator] 60 | /// to get this using the [anchorKeyId] 61 | final Anchor? anchor; 62 | 63 | /// function called when user type on anchor position 64 | final Function? onTapAnchor; 65 | 66 | /// factory to create the all background with the hole 67 | /// Use one of these 68 | /// - [AnchoredCircleHoleHelper.anchorFactory] 69 | /// - [AnchoredRectHoleHelper.anchorFactory] 70 | /// or create yours 71 | final AnchorWidgetFactory widgetFactory; 72 | 73 | const AnchoredHelper({ 74 | this.onLeftBtnTap, 75 | this.onRightTap, 76 | this.rightBtnText, 77 | this.leftBtnText, 78 | this.title, 79 | this.description, 80 | this.onError, 81 | this.leftBtnStyle, 82 | this.rightBtnStyle, 83 | this.onTapAnchor, 84 | this.onTapBackground, 85 | Key? key, 86 | required this.bgColor, 87 | this.anchor, 88 | this.widgetFactory = AnchoredCircleHoleHelper.anchorFactory, 89 | }) : super(key: key); 90 | 91 | @override 92 | _AnchoredHelperState createState() => _AnchoredHelperState(); 93 | } 94 | 95 | class _AnchoredHelperState extends State 96 | with TickerProviderStateMixin { 97 | late AnimationController anchorAnimationController, fadeAnimController; 98 | late Animation backgroundAnimation; 99 | 100 | Animation? titleOpacityAnimation, titleSizeAnimation; 101 | Animation? descriptionOpacityAnimation, descriptionSizeAnimation; 102 | Animation? btnOpacityAnimation, btnSizeAnimation; 103 | 104 | Anchor get anchor => widget.anchor ?? AnchorHelperWrapper.of(context)!.anchor; 105 | 106 | @override 107 | void initState() { 108 | super.initState(); 109 | anchorAnimationController = AnimationController( 110 | vsync: this, duration: const Duration(milliseconds: 650)) 111 | ..repeat(reverse: true); 112 | fadeAnimController = AnimationController( 113 | vsync: this, duration: const Duration(milliseconds: 1500)); 114 | backgroundAnimation = CurvedAnimation( 115 | parent: fadeAnimController, 116 | curve: const Interval(0, .2, curve: Curves.easeIn), 117 | ); 118 | titleOpacityAnimation = CurvedAnimation( 119 | parent: fadeAnimController, 120 | curve: const Interval(.4, .5, curve: Curves.easeIn), 121 | ); 122 | titleSizeAnimation = CurvedAnimation( 123 | parent: fadeAnimController, 124 | curve: const Interval(.4, .6, curve: Curves.easeInOutBack), 125 | ); 126 | descriptionOpacityAnimation = CurvedAnimation( 127 | parent: fadeAnimController, 128 | curve: const Interval(.5, .6, curve: Curves.easeIn), 129 | ); 130 | descriptionSizeAnimation = CurvedAnimation( 131 | parent: fadeAnimController, 132 | curve: const Interval(.5, .7, curve: Curves.easeInOutBack), 133 | ); 134 | btnOpacityAnimation = CurvedAnimation( 135 | parent: fadeAnimController, 136 | curve: const Interval(.8, .9, curve: Curves.easeIn), 137 | ); 138 | btnSizeAnimation = CurvedAnimation( 139 | parent: fadeAnimController, 140 | curve: const Interval(.8, 1, curve: Curves.easeInOutBack), 141 | ); 142 | fadeAnimController.forward(); 143 | } 144 | 145 | @override 146 | void dispose() { 147 | anchorAnimationController.stop(); 148 | fadeAnimController.stop(); 149 | anchorAnimationController.dispose(); 150 | fadeAnimController.dispose(); 151 | super.dispose(); 152 | } 153 | 154 | @override 155 | Widget build(BuildContext context) { 156 | // print("anchor: ${anchor.offset} ${anchor.size}: ${anchor.rect}"); 157 | return GestureDetector( 158 | onTap: () async { 159 | if (widget.onTapBackground != null) { 160 | HapticFeedback.selectionClick(); 161 | await fadeAnimController.reverse(); 162 | widget.onTapBackground!.call(); 163 | } 164 | }, 165 | child: Material( 166 | color: Colors.transparent, 167 | child: Stack( 168 | children: [ 169 | Positioned.fill( 170 | child: FadeTransition( 171 | opacity: backgroundAnimation, 172 | child: widget.widgetFactory.create( 173 | currentPos: anchor.offset, 174 | anchorSize: anchor.size, 175 | bgColor: widget.bgColor, 176 | listenable: anchorAnimationController, 177 | onTap: () async { 178 | if (widget.onTapAnchor != null) { 179 | HapticFeedback.selectionClick(); 180 | await fadeAnimController.reverse(); 181 | widget.onTapAnchor!(); 182 | } 183 | }, 184 | ), 185 | ), 186 | ), 187 | Positioned.fromRect( 188 | rect: anchor.rect, 189 | child: LayoutBuilder( 190 | builder: (context, constraints) => Padding( 191 | padding: const EdgeInsets.symmetric( 192 | horizontal: 16.0, vertical: 8.0), 193 | child: ConstrainedBox( 194 | constraints: BoxConstraints( 195 | minHeight: constraints.maxHeight, 196 | ), 197 | child: Column( 198 | mainAxisAlignment: MainAxisAlignment.center, 199 | crossAxisAlignment: CrossAxisAlignment.center, 200 | mainAxisSize: MainAxisSize.min, 201 | children: [ 202 | if (widget.title != null) 203 | Padding( 204 | padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), 205 | child: PopAnimation( 206 | animation: fadeAnimController, 207 | opacityAnim: titleOpacityAnimation, 208 | sizeAnim: titleSizeAnimation, 209 | child: widget.title!, 210 | ), 211 | ), 212 | if (widget.description != null) 213 | Padding( 214 | padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), 215 | child: PopAnimation( 216 | animation: fadeAnimController, 217 | opacityAnim: descriptionOpacityAnimation, 218 | sizeAnim: descriptionSizeAnimation, 219 | child: widget.description!, 220 | ), 221 | ), 222 | const SizedBox(height: 24), 223 | Row( 224 | mainAxisAlignment: MainAxisAlignment.center, 225 | crossAxisAlignment: CrossAxisAlignment.center, 226 | children: [ 227 | if (widget.leftBtnText != null && 228 | widget.onRightTap != null) 229 | Expanded( 230 | child: PopAnimation( 231 | animation: fadeAnimController, 232 | opacityAnim: btnOpacityAnimation, 233 | sizeAnim: btnSizeAnimation, 234 | child: _buildEditableBordered( 235 | widget.leftBtnText!, 236 | widget.onRightTap!, 237 | widget.leftBtnStyle, 238 | ), 239 | ), 240 | ), 241 | const SizedBox(width: 16), 242 | if (widget.rightBtnText != null && 243 | widget.onLeftBtnTap != null) 244 | Expanded( 245 | child: PopAnimation( 246 | animation: fadeAnimController, 247 | opacityAnim: btnOpacityAnimation, 248 | sizeAnim: btnSizeAnimation, 249 | child: _buildEditableBordered( 250 | widget.rightBtnText!, 251 | widget.onLeftBtnTap!, 252 | widget.rightBtnStyle, 253 | ), 254 | ), 255 | ), 256 | ], 257 | ), 258 | ], 259 | ), 260 | ), 261 | ), 262 | ), 263 | ), 264 | ], 265 | ), 266 | ), 267 | ); 268 | } 269 | 270 | Widget _buildEditableBordered( 271 | Text text, 272 | Function onTap, 273 | ButtonStyle? outlineButtonStyle, 274 | ) { 275 | return OutlinedButton( 276 | onPressed: () async { 277 | HapticFeedback.selectionClick(); 278 | await fadeAnimController.reverse(); 279 | onTap(); 280 | }, 281 | style: outlineButtonStyle, 282 | child: text, 283 | ); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /lib/anchored/painters/anchor_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Used by [AnchoredWidget] to paint the background 4 | abstract class AnchorWidgetFactory { 5 | const AnchorWidgetFactory(); 6 | 7 | // @factory 8 | Widget create({ 9 | final Offset? currentPos, 10 | final Size? anchorSize, 11 | final Color? bgColor, 12 | final Function? onTap, 13 | final Listenable? listenable, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /lib/anchored/painters/anchored_circle_painter.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | import 'dart:math'; 3 | 4 | import 'package:flutter/material.dart'; 5 | 6 | class AnchoredCirclePainter extends CustomPainter { 7 | final Offset? currentPos; 8 | 9 | final double padding; 10 | 11 | final Size? anchorSize; 12 | 13 | final Color? bgColor; 14 | 15 | double? radius; 16 | Offset? center; 17 | 18 | double circle1Width, circle2Width; 19 | 20 | AnchoredCirclePainter({ 21 | this.currentPos, 22 | this.anchorSize, 23 | this.padding = 0, 24 | this.bgColor, 25 | this.circle1Width = 64, 26 | this.circle2Width = 100, 27 | }); 28 | 29 | @override 30 | void paint(Canvas canvas, Size size) { 31 | Paint clearPainter = Paint() 32 | ..blendMode = BlendMode.clear 33 | ..isAntiAlias = true; 34 | Paint bgPainter = Paint() 35 | ..color = bgColor! 36 | ..style = PaintingStyle.fill 37 | ..isAntiAlias = true; 38 | Paint circle1Painter = Paint() 39 | ..color = Colors.white.withOpacity(.3) 40 | ..style = PaintingStyle.stroke 41 | ..strokeWidth = circle1Width 42 | ..isAntiAlias = true; 43 | Paint circle2Painter = Paint() 44 | ..color = Colors.white.withOpacity(.4) 45 | ..style = PaintingStyle.stroke 46 | ..strokeWidth = circle2Width 47 | ..isAntiAlias = true; 48 | canvas.saveLayer(Offset.zero & size, Paint()); 49 | canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), bgPainter); 50 | // canvas.drawCircle(currentPos, radius, clearPainter); 51 | // canvas.drawRect(currentPos & anchorSize, clearPainter); 52 | radius = radiusCalc; 53 | center = centerCalc; 54 | canvas.drawCircle(center!, radius! + padding, circle1Painter); 55 | canvas.drawCircle(center!, radius! + padding, circle2Painter); 56 | canvas.drawCircle(center!, radius! + padding, clearPainter); 57 | canvas.restore(); 58 | } 59 | 60 | @pragma('vm:prefer-inline') 61 | double get radiusCalc => 62 | sqrt(pow(anchorSize!.width, 2) + pow(anchorSize!.height, 2)) / 2; 63 | 64 | @pragma('vm:prefer-inline') 65 | Offset get centerCalc => currentPos!.translate( 66 | anchorSize!.width / 2, 67 | anchorSize!.height / 2, 68 | ); 69 | 70 | @override 71 | bool shouldRepaint(AnchoredCirclePainter oldDelegate) { 72 | return oldDelegate.currentPos != currentPos || 73 | oldDelegate.circle1Width != circle1Width || 74 | oldDelegate.circle2Width != circle2Width || 75 | oldDelegate.bgColor != bgColor; 76 | } 77 | 78 | @override 79 | bool hitTest(Offset position) { 80 | if (currentPos == null) return false; 81 | var distance = (position - (center ?? centerCalc)).distance; 82 | if (distance <= (radius ?? radiusCalc)) { 83 | return true; 84 | } 85 | return false; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/anchored/painters/anchored_rect_painter.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | import 'package:flutter/material.dart'; 3 | 4 | extension AddSize on Size { 5 | withPadding(double padding) { 6 | return Size(width + padding, height + padding); 7 | } 8 | } 9 | 10 | class AnchoredRectPainter extends CustomPainter { 11 | final Offset? currentPos; 12 | 13 | final double padding; 14 | 15 | final Size? anchorSize; 16 | 17 | final Color? bgColor; 18 | 19 | double? radius; 20 | Offset? center; 21 | 22 | double rect1Width, rect2Width; 23 | 24 | AnchoredRectPainter({ 25 | this.currentPos, 26 | this.anchorSize, 27 | this.padding = 0, 28 | this.bgColor, 29 | this.rect1Width = 32, 30 | this.rect2Width = 64, 31 | }); 32 | 33 | @override 34 | void paint(Canvas canvas, Size size) { 35 | Paint clearPainter = Paint() 36 | ..blendMode = BlendMode.clear 37 | ..isAntiAlias = true; 38 | Paint bgPainter = Paint() 39 | ..color = bgColor! 40 | ..style = PaintingStyle.fill 41 | ..isAntiAlias = true; 42 | Paint rect1Painter = Paint() 43 | ..color = Colors.white.withOpacity(.3) 44 | ..style = PaintingStyle.stroke 45 | ..strokeWidth = rect1Width 46 | ..isAntiAlias = true; 47 | Paint rect2Painter = Paint() 48 | ..color = Colors.white.withOpacity(.4) 49 | ..style = PaintingStyle.stroke 50 | ..strokeWidth = rect2Width 51 | ..isAntiAlias = true; 52 | canvas.saveLayer(Offset.zero & size, Paint()); 53 | canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), bgPainter); 54 | 55 | center = centerCalc; 56 | final rect = Rect.fromLTWH( 57 | currentPos!.dx, 58 | currentPos!.dy, 59 | anchorSize!.width, 60 | anchorSize!.height, 61 | ); 62 | canvas.drawRect(rect, rect1Painter); 63 | canvas.drawRect(rect, rect2Painter); 64 | canvas.drawRect(currentPos! & anchorSize!, clearPainter); 65 | canvas.restore(); 66 | } 67 | 68 | @pragma('vm:prefer-inline') 69 | Offset get centerCalc => currentPos!.translate( 70 | anchorSize!.width / 2, 71 | anchorSize!.height / 2, 72 | ); 73 | 74 | @override 75 | bool shouldRepaint(AnchoredRectPainter oldDelegate) { 76 | return oldDelegate.currentPos != currentPos || 77 | oldDelegate.rect1Width != rect1Width || 78 | oldDelegate.rect2Width != rect2Width || 79 | oldDelegate.bgColor != bgColor; 80 | } 81 | 82 | @override 83 | bool hitTest(Offset position) { 84 | if (currentPos == null) return false; 85 | return (position.dx >= currentPos!.dx && 86 | position.dx <= (currentPos!.dx + anchorSize!.width) && 87 | position.dy >= currentPos!.dy && 88 | position.dy <= (currentPos!.dy + anchorSize!.height)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/anchored/painters/animated_anchor.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | import 'package:flutter/material.dart'; 3 | 4 | /// Helper explaining a widget 5 | /// this creates a full background widget with a hole of the size of the aimed 6 | /// widget. 7 | abstract class BasicAnchoredHoleHelper extends AnimatedWidget { 8 | final Offset? currentPos; 9 | final double padding; 10 | final Size? anchorSize; 11 | final Color? bgColor; 12 | final Function? onTap; 13 | 14 | final Animation _stroke1Animation, _stroke2Animation; 15 | 16 | CustomPainter get customPainter; 17 | 18 | Animation get stroke1Animation => _stroke1Animation; 19 | 20 | Animation get stroke2Animation => _stroke2Animation; 21 | 22 | BasicAnchoredHoleHelper({ 23 | Key? key, 24 | required this.currentPos, 25 | required this.padding, 26 | required this.bgColor, 27 | required this.anchorSize, 28 | required Listenable listenable, 29 | this.onTap, 30 | }) : _stroke1Animation = CurvedAnimation( 31 | parent: listenable as Animation, 32 | curve: Curves.ease, 33 | ), 34 | _stroke2Animation = CurvedAnimation( 35 | parent: listenable, 36 | curve: const Interval(0, .8, curve: Curves.ease), 37 | ), 38 | super(key: key, listenable: listenable); 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return GestureDetector( 43 | onTap: () { 44 | if (onTap != null) { 45 | onTap!(); 46 | } 47 | }, 48 | child: SizedBox( 49 | child: CustomPaint( 50 | painter: customPainter, 51 | ), 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/anchored/painters/animated_circle.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'anchor_painter.dart'; 5 | import 'anchored_circle_painter.dart'; 6 | import 'animated_anchor.dart'; 7 | 8 | class _AnchoredHoleHelperFactory extends AnchorWidgetFactory { 9 | const _AnchoredHoleHelperFactory(); 10 | 11 | @override 12 | Widget create({ 13 | Offset? currentPos, 14 | Size? anchorSize, 15 | Color? bgColor, 16 | Function? onTap, 17 | Listenable? listenable, 18 | }) { 19 | return AnchoredCircleHoleHelper( 20 | currentPos: currentPos, 21 | padding: 8, 22 | bgColor: bgColor, 23 | anchorSize: anchorSize, 24 | listenable: listenable!, 25 | onTap: onTap, 26 | ); 27 | } 28 | } 29 | 30 | const _circle1Width = 88; 31 | const _circle2Width = 140; 32 | 33 | /// Helper explaining a widget 34 | /// this creates a full background widget with a hole of the size of the aimed 35 | /// widget. 36 | class AnchoredCircleHoleHelper extends BasicAnchoredHoleHelper { 37 | static const AnchorWidgetFactory anchorFactory = _AnchoredHoleHelperFactory(); 38 | 39 | AnchoredCircleHoleHelper({ 40 | Key? key, 41 | required Offset? currentPos, 42 | required double padding, 43 | required Color? bgColor, 44 | required Size? anchorSize, 45 | required Listenable listenable, 46 | Function? onTap, 47 | }) : super( 48 | key: key, 49 | listenable: listenable, 50 | currentPos: currentPos, 51 | padding: padding, 52 | bgColor: bgColor, 53 | anchorSize: anchorSize, 54 | onTap: onTap, 55 | ); 56 | 57 | @override 58 | CustomPainter get customPainter => AnchoredCirclePainter( 59 | currentPos: currentPos, 60 | anchorSize: anchorSize, 61 | padding: padding, 62 | bgColor: bgColor, 63 | circle1Width: stroke1Animation.value * _circle1Width, 64 | circle2Width: stroke2Animation.value * _circle2Width, 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /lib/anchored/painters/animated_rect.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'anchor_painter.dart'; 5 | import 'anchored_rect_painter.dart'; 6 | import 'animated_anchor.dart'; 7 | 8 | class _AnchoredRectHoleHelperFactory extends AnchorWidgetFactory { 9 | const _AnchoredRectHoleHelperFactory(); 10 | 11 | @override 12 | Widget create({ 13 | Offset? currentPos, 14 | Size? anchorSize, 15 | Color? bgColor, 16 | Function? onTap, 17 | Listenable? listenable, 18 | }) { 19 | return AnchoredRectHoleHelper( 20 | currentPos: currentPos, 21 | padding: 8, 22 | bgColor: bgColor, 23 | anchorSize: anchorSize, 24 | listenable: listenable!, 25 | onTap: onTap, 26 | ); 27 | } 28 | } 29 | 30 | const _rect1Width = 16; 31 | const _rect2Width = 32; 32 | 33 | /// Helper explaining a widget 34 | /// this creates a full background widget with a hole of the size of the aimed 35 | /// widget. 36 | class AnchoredRectHoleHelper extends BasicAnchoredHoleHelper { 37 | static const AnchorWidgetFactory anchorFactory = 38 | _AnchoredRectHoleHelperFactory(); 39 | 40 | AnchoredRectHoleHelper({ 41 | Key? key, 42 | required Offset? currentPos, 43 | required double padding, 44 | required Color? bgColor, 45 | required Size? anchorSize, 46 | required Listenable listenable, 47 | Function? onTap, 48 | }) : super( 49 | key: key, 50 | listenable: listenable, 51 | currentPos: currentPos, 52 | padding: padding, 53 | bgColor: bgColor, 54 | anchorSize: anchorSize, 55 | onTap: onTap, 56 | ); 57 | 58 | @override 59 | CustomPainter get customPainter => AnchoredRectPainter( 60 | currentPos: currentPos, 61 | anchorSize: anchorSize, 62 | padding: padding, 63 | bgColor: bgColor, 64 | rect1Width: stroke1Animation.value * _rect1Width, 65 | rect2Width: stroke2Animation.value * _rect2Width, 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /lib/animations/pop_anim.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PopAnimation extends StatelessWidget { 4 | final Animation? sizeAnim; 5 | final Animation? opacityAnim; 6 | final Widget child; 7 | final Listenable animation; 8 | 9 | const PopAnimation({ 10 | Key? key, 11 | this.sizeAnim, 12 | this.opacityAnim, 13 | required this.child, 14 | required this.animation, 15 | }) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return AnimatedBuilder( 20 | animation: animation, 21 | builder: (context, child) => Transform.translate( 22 | offset: Offset(0, -40 + ((sizeAnim?.value ?? 0) * 40)), 23 | child: Transform.scale( 24 | scale: sizeAnim?.value ?? 0, 25 | child: Opacity( 26 | opacity: opacityAnim?.value ?? 0, 27 | child: child, 28 | ), 29 | ), 30 | ), 31 | child: child, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/helper_orchestrator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:pal_widgets/services/element_finder.dart'; 3 | 4 | import 'anchored/anchor_model.dart'; 5 | import 'anchored/anchored_helper_widget.dart'; 6 | import 'services/overlay_helper.dart'; 7 | 8 | /// used to align the helper to the top, bottom, left or right of the anchor 9 | enum HelperAlignment { top, bottom, left, right } 10 | 11 | class _HelperOrchestratorScope extends InheritedWidget { 12 | final HelperOrchestratorState _helperOrchestratorState; 13 | 14 | const _HelperOrchestratorScope({ 15 | Key? key, 16 | required Widget child, 17 | required HelperOrchestratorState helperOrchestratorState, 18 | }) : _helperOrchestratorState = helperOrchestratorState, 19 | super(key: key, child: child); 20 | 21 | @override 22 | bool updateShouldNotify(_HelperOrchestratorScope old) => 23 | _helperOrchestratorState != old._helperOrchestratorState; 24 | } 25 | 26 | /// Manages Pal widgets to display an [Overlay] over your pages 27 | /// this will allow you to register widgets and show [AnchoredHelper] and others 28 | /// Pal onboarding widgets 29 | class HelperOrchestrator extends StatefulWidget { 30 | final Widget child; 31 | final GlobalKey? navigatorKey; 32 | 33 | const HelperOrchestrator({ 34 | Key? key, 35 | required this.child, 36 | this.navigatorKey, 37 | }) : super(key: key); 38 | 39 | /// If there is no [HelperOrchestrator] in scope 40 | /// 41 | /// See also: 42 | /// 43 | /// * [maybeOf], which is a similar function but will return null instead of 44 | /// throwing if there is no [HelperOrchestrator] ancestor. 45 | static HelperOrchestratorState of(BuildContext context) { 46 | assert(debugCheckHasScaffoldMessenger(context)); 47 | 48 | final _HelperOrchestratorScope scope = 49 | context.dependOnInheritedWidgetOfExactType<_HelperOrchestratorScope>()!; 50 | return scope._helperOrchestratorState; 51 | } 52 | 53 | /// The state from the closest instance of this class that encloses the given 54 | /// context, if any. 55 | /// 56 | /// Will return null if a [HelperOrchestrator] is not found in the given context. 57 | /// 58 | /// See also: 59 | /// 60 | /// * [of], which is a similar function, except that it will throw an 61 | /// exception if a [HelperOrchestrator] is not found in the given context. 62 | static HelperOrchestratorState? maybeOf(BuildContext? context) { 63 | if (context == null) { 64 | return null; 65 | } 66 | final _HelperOrchestratorScope? scope = 67 | context.dependOnInheritedWidgetOfExactType<_HelperOrchestratorScope>(); 68 | return scope?._helperOrchestratorState; 69 | } 70 | 71 | @override 72 | HelperOrchestratorState createState() => HelperOrchestratorState(); 73 | } 74 | 75 | /// State for [HelperOrchestrator] 76 | /// 77 | /// Can display an helper as overlay above a child using 78 | /// the current [BuildContext] and [HelperOrchestrator.of] 79 | class HelperOrchestratorState extends State { 80 | final Map keys = {}; 81 | final OverlayHelper _overlayHelper = OverlayHelper(); 82 | ElementFinder? _elementFinder; 83 | 84 | @override 85 | void initState() { 86 | super.initState(); 87 | } 88 | 89 | @override 90 | void didChangeDependencies() { 91 | super.didChangeDependencies(); 92 | if (widget.navigatorKey != null) { 93 | _elementFinder = ElementFinder(navigatorKey: widget.navigatorKey); 94 | } else { 95 | _elementFinder = ElementFinder(buildContext: context); 96 | } 97 | } 98 | 99 | /// generate a [key] that will be registered within HelperOrchestrator 100 | /// this keyId is associated with the key and can be found again 101 | /// using [getAnchorKey] 102 | /// The key will be used to find the element position when requesting 103 | /// [showAnchoredHelper] 104 | Key generateKey(String key) { 105 | // final uniqueKey = GlobalKey(debugLabel: key); 106 | final uniqueKey = ValueKey(key); 107 | keys[key] = uniqueKey; 108 | return uniqueKey; 109 | } 110 | 111 | /// Returns the [Key] from the KeyId if found. 112 | /// this throws if not found. 113 | Key getAnchorKey(String keyId) { 114 | if (keys.containsKey(keyId)) { 115 | return keys[keyId]!; 116 | } 117 | throw 'Key not found'; 118 | } 119 | 120 | /// This shows an [AnchoredHelper] above your page as overlay 121 | /// 122 | /// requires [anchorKeyId] that must have been generated within a widget using 123 | /// ```dart 124 | /// HelperOrchestrator.of(context).generateKey('myKeyId') 125 | /// ``` 126 | Future showAnchoredHelper( 127 | String anchorKeyId, 128 | AnchoredHelper helper, { 129 | HelperAlignment? align, 130 | bool isInModal = false, 131 | }) async { 132 | try { 133 | //final key = getAnchorKey(anchorKeyId) as ValueKey; 134 | final anchor = 135 | await findAnchor(anchorKeyId, align: align, isInModal: isInModal); 136 | if (anchor == null) { 137 | debugPrint("anchor cannot be found. show anchored failed"); 138 | return; 139 | } 140 | _overlayHelper.showHelper( 141 | context, 142 | (context) => AnchorHelperWrapper( 143 | anchor: anchor, 144 | child: helper, 145 | ), 146 | ); 147 | } catch (e, s) { 148 | debugPrint("show anchored helper failed: $e $s"); 149 | } 150 | } 151 | 152 | /// Returns an [Anchor] wich contains position, size and rect of the widget 153 | /// containing the key. 154 | /// 155 | /// this requires an [anchorKeyId] to search within our keys 156 | Future findAnchor( 157 | String anchorKeyId, { 158 | HelperAlignment? align, 159 | bool isInModal = false, 160 | }) async { 161 | final element = _elementFinder! // 162 | .searchChildElementByKey(getAnchorKey(anchorKeyId), 163 | isInModal: isInModal); 164 | if (element == null || element.bounds == null) { 165 | debugPrint("anchor not found"); 166 | return null; 167 | } 168 | final anchorSize = element.bounds!.size; 169 | final currentPos = element.offset!; 170 | if (align != null) { 171 | return Anchor( 172 | size: anchorSize, 173 | offset: currentPos, 174 | rect: _elementFinder!.getSpaceFromAlignment(align, element), 175 | ); 176 | } 177 | return Anchor( 178 | size: anchorSize, 179 | offset: currentPos, 180 | rect: _elementFinder!.getLargestAvailableSpace(element), 181 | ); 182 | } 183 | 184 | /// Hide the current overlayed helper 185 | /// this do nothing if there is no helper overlayed. 186 | void hideHelper() { 187 | _overlayHelper.popHelper(); 188 | } 189 | 190 | @override 191 | Widget build(BuildContext context) { 192 | return _HelperOrchestratorScope( 193 | helperOrchestratorState: this, 194 | child: widget.child, 195 | ); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /lib/pal_widgets.dart: -------------------------------------------------------------------------------- 1 | library pal_widgets; 2 | 3 | export 'helper_orchestrator.dart'; 4 | export 'anchored/anchored_helper_widget.dart' show AnchoredHelper; 5 | export 'anchored/painters/animated_circle.dart'; 6 | export 'anchored/painters/animated_rect.dart'; 7 | -------------------------------------------------------------------------------- /lib/services/element_finder.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart' show IterableExtension; 2 | import 'package:flutter/material.dart'; 3 | import 'package:pal_widgets/helper_orchestrator.dart'; 4 | 5 | const minWritableSpace = 100; 6 | 7 | /// This class helps you find element within your app 8 | /// use [searchChildElement] with a String containing key value 9 | /// result is available in [result] and returns an [Element] 10 | class ElementFinder { 11 | // Prefer use the navigatorContext to get full context tree 12 | final BuildContext? buildContext; 13 | final GlobalKey? navigatorKey; 14 | 15 | ElementFinder({this.buildContext, this.navigatorKey}); 16 | 17 | BuildContext get context { 18 | if (buildContext != null) { 19 | return buildContext!; 20 | } 21 | return navigatorKey!.currentContext!; 22 | } 23 | 24 | Size get screenSize => MediaQuery.of(context).size; 25 | 26 | // this method scan all child recursively to get all widget bounds we could select for an helper 27 | Map scan({Key? omitChildsOf, bool debugMode = false}) { 28 | Map results = {}; 29 | context.visitChildElements((element) => _scanChildElement( 30 | context.findRenderObject(), element, results, 31 | omitChildsOf: omitChildsOf, debugMode: debugMode)); 32 | return results; 33 | } 34 | 35 | // List all pages from this context 36 | List scanPages() { 37 | var pages = []; 38 | context.visitChildElements((element) => 39 | _scanPageChildElement(element, pages as List)); 40 | return pages as List; 41 | } 42 | 43 | // this method scan all child recursively to find a widget having a key == searchedKey 44 | ElementModel? searchChildElement(String? key) { 45 | ElementModel? result; 46 | context.visitChildElements((element) => 47 | result = _searchChildElement(context.findRenderObject(), element, key)); 48 | if (result == null) { 49 | return ElementModel.empty(); 50 | } 51 | return result; 52 | } 53 | 54 | ElementModel? searchChildElementByKey( 55 | Key key, { 56 | required bool isInModal, 57 | }) { 58 | ElementModel? result; 59 | context.visitChildElements((element) => result = _searchChildElementByKey( 60 | context.findRenderObject(), 61 | element, 62 | key, 63 | isInModal: isInModal, 64 | )); 65 | if (result == null) { 66 | return ElementModel.empty(); 67 | } 68 | return result; 69 | } 70 | 71 | Rect getSpaceFromAlignment(HelperAlignment align, ElementModel elementModel) { 72 | var parentObject = context.findRenderObject()!; 73 | var element = elementModel.element!; 74 | // var translation = element.renderObject!.getTransformTo(parentObject).getTranslation(); 75 | var objectX = elementModel.offset!.dx; 76 | var objectEndX = objectX + element.size!.width; 77 | var objectY = elementModel.offset!.dy; 78 | var objectEndY = objectY + element.size!.height; 79 | var layerRect = parentObject.paintBounds; 80 | switch (align) { 81 | case HelperAlignment.top: 82 | return Rect.fromLTWH(0, 0, layerRect.width, objectY); 83 | case HelperAlignment.bottom: 84 | return Rect.fromLTWH(0, objectEndY, layerRect.width, layerRect.height); 85 | case HelperAlignment.left: 86 | return Rect.fromLTWH(0, 0, objectX, layerRect.height); 87 | case HelperAlignment.right: 88 | return Rect.fromLTWH( 89 | objectEndX, 0, layerRect.width - objectEndX, layerRect.height); 90 | } 91 | } 92 | 93 | /// This functions search for the maximum rect available space 94 | /// We use it for example to find the most available space to write a text in our anchored helper 95 | Rect getLargestAvailableSpace(ElementModel elementModel) { 96 | var parentObject = context.findRenderObject()!; 97 | var element = elementModel.element!; 98 | var translation = 99 | element.renderObject!.getTransformTo(parentObject).getTranslation(); 100 | var objectX = translation.x; 101 | var objectEndX = objectX + element.size!.width; 102 | var objectY = translation.y; 103 | var objectEndY = objectY + element.size!.height; 104 | var layerRect = parentObject.paintBounds; 105 | 106 | Rect availableHSpace; 107 | Rect availableWSpace; 108 | if (objectY > layerRect.height - objectEndY) { 109 | availableHSpace = Rect.fromLTWH(0, 0, layerRect.width, objectY); 110 | } else { 111 | availableHSpace = Rect.fromLTWH( 112 | 0, objectEndY, layerRect.width, layerRect.height - objectEndY); 113 | } 114 | if (objectX > layerRect.width - objectEndX) { 115 | availableWSpace = Rect.fromLTWH(0, 0, objectX, layerRect.height); 116 | } else { 117 | availableWSpace = Rect.fromLTWH( 118 | objectEndX, 0, layerRect.width - objectEndX, layerRect.height); 119 | } 120 | // check area to use the largest 121 | var availableHSpaceArea = 122 | availableHSpace.size.width * availableHSpace.size.height; 123 | var availableWSpaceArea = 124 | availableWSpace.size.width * availableWSpace.size.height; 125 | if (availableWSpaceArea > availableHSpaceArea && 126 | availableWSpace.width > minWritableSpace) { 127 | return availableWSpace; 128 | } 129 | return availableHSpace; 130 | } 131 | 132 | // ----------------------------------------------------------- 133 | // private 134 | // ----------------------------------------------------------- 135 | 136 | // ERROR 137 | ElementModel? _searchChildElement( 138 | RenderObject? parentObject, Element element, String? key, 139 | {int n = 0}) { 140 | if (element.widget.key != null && 141 | element.widget.key.toString().contains(key!)) { 142 | try { 143 | // if render element has bounds lets take it 144 | var res = _createElementModel(parentObject, element, false); 145 | return res; 146 | } catch (_) {} 147 | } 148 | ElementModel? result; 149 | element.visitChildElements((visitor) { 150 | var res = _searchChildElement(parentObject, visitor, key, n: n + 1); 151 | if (res != null) result = res; 152 | }); 153 | return result; 154 | } 155 | 156 | ElementModel? _searchChildElementByKey( 157 | RenderObject? parentObject, 158 | Element element, 159 | Key key, { 160 | int n = 0, 161 | required bool isInModal, 162 | }) { 163 | if (element.widget.key != null && element.widget.key == key) { 164 | try { 165 | // if render element has bounds lets take it 166 | return _createElementModel(parentObject, element, isInModal); 167 | } catch (_) {} 168 | } 169 | ElementModel? result; 170 | element.visitChildElements((visitor) { 171 | var res = _searchChildElementByKey( 172 | parentObject, 173 | visitor, 174 | key, 175 | n: n + 1, 176 | isInModal: isInModal, 177 | ); 178 | if (res != null) result = res; 179 | }); 180 | return result; 181 | } 182 | 183 | // omits elements with key starting with anything other than [< 184 | // flutter makes key with "[<_myKey_>]" for our keys 185 | // scan all elements in the current page tree and add their bounds to the results map 186 | _scanChildElement(RenderObject? parentObject, Element element, 187 | Map results, 188 | {int n = 0, Key? omitChildsOf, bool debugMode = true}) { 189 | if (debugMode) { 190 | var nbChilds = element.debugDescribeChildren().length; 191 | var pre = StringBuffer(); 192 | for (int i = 0; i < n; i++) { 193 | pre.write(" "); 194 | } 195 | debugPrint("$pre ${element.widget.runtimeType} $n => $nbChilds "); 196 | } 197 | if (element.widget.key != null && 198 | omitChildsOf != null && 199 | element.widget.key.toString() == omitChildsOf.toString()) { 200 | return; 201 | } 202 | if (element.widget.key != null && 203 | element.widget.key.toString().startsWith("[<") && 204 | !results.containsKey(element.widget.key.toString())) { 205 | if (debugMode) { 206 | debugPrint(" added ${element.widget.key.toString()} : $n"); 207 | } 208 | try { 209 | var model = _createElementModel(parentObject, element, false); 210 | if (results.values.firstWhereOrNull((element) => 211 | element.bounds == model.bounds && 212 | element.offset == model.offset) == 213 | null) { 214 | results.putIfAbsent(element.widget.key.toString(), () => model); 215 | } 216 | } catch (e) { 217 | debugPrint(" error while getting element bounds:"); 218 | debugPrint("$e"); 219 | } 220 | } 221 | element.visitChildElements((visitor) => _scanChildElement( 222 | parentObject, visitor, results, 223 | n: n + 1, omitChildsOf: omitChildsOf, debugMode: debugMode)); 224 | } 225 | 226 | // search first entries that contains our pages 227 | _scanPageChildElement(Element element, List pages) { 228 | if (element.runtimeType.toString() == "_Theatre") { 229 | element.visitChildElements((visitor) => pages.add(PageElement(element))); 230 | } else { 231 | element.visitChildElements( 232 | (visitor) => _scanPageChildElement(element, pages)); 233 | } 234 | } 235 | 236 | ElementModel _createElementModel( 237 | RenderObject? parentObject, 238 | Element element, 239 | bool isInModal, 240 | ) { 241 | try { 242 | final mScreenSize = screenSize; 243 | var renderObject = element.findRenderObject()!; 244 | 245 | var bounds = renderObject.paintBounds; 246 | var translation = renderObject // 247 | .getTransformTo(parentObject) 248 | .getTranslation(); 249 | // final parentBounds = parentObject!.paintBounds; 250 | if (isInModal) { 251 | final parentRenderObject = renderObject.parent!; 252 | final parentBounds = parentRenderObject.paintBounds; 253 | if (parentBounds.height < mScreenSize.height) { 254 | translation.y += 255 | mScreenSize.height - parentBounds.height - bounds.height / 2; 256 | } 257 | } 258 | var offset = Offset(translation.x, translation.y); 259 | 260 | return ElementModel( 261 | element.widget.key.toString(), 262 | bounds, 263 | offset, 264 | element.widget.runtimeType, 265 | element: element, 266 | ); 267 | } catch (e, s) { 268 | debugPrint("Error while creating element model: $e $s"); 269 | rethrow; 270 | } 271 | } 272 | } 273 | 274 | class PageElement { 275 | Element element; 276 | 277 | PageElement(this.element); 278 | } 279 | 280 | class ElementModel { 281 | String? key; 282 | 283 | Rect? bounds; 284 | 285 | Offset? offset; 286 | 287 | Element? element; 288 | 289 | @override 290 | Type runtimeType; 291 | 292 | ElementModel( 293 | this.key, 294 | this.bounds, 295 | this.offset, 296 | this.runtimeType, { 297 | this.element, 298 | }); 299 | 300 | factory ElementModel.empty() => ElementModel(null, null, null, ElementModel); 301 | } 302 | -------------------------------------------------------------------------------- /lib/services/overlay_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class OverlayHelper { 4 | OverlayEntry? overlayEntry; 5 | 6 | OverlayHelper(); 7 | 8 | showHelper(BuildContext context, WidgetBuilder widgetBuilder) { 9 | popHelper(); 10 | overlayEntry = OverlayEntry( 11 | opaque: false, 12 | builder: widgetBuilder, 13 | ); 14 | final overlay = Overlay.maybeOf(context); 15 | if (overlay != null) { 16 | overlay.insert(overlayEntry!); 17 | } 18 | } 19 | 20 | bool popHelper() { 21 | if (overlayEntry != null) { 22 | overlayEntry!.remove(); 23 | overlayEntry = null; 24 | return true; 25 | } 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pal-widgets.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: "direct main" 38 | description: 39 | name: collection 40 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.18.0" 44 | fake_async: 45 | dependency: transitive 46 | description: 47 | name: fake_async 48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.3.1" 52 | flutter: 53 | dependency: "direct main" 54 | description: flutter 55 | source: sdk 56 | version: "0.0.0" 57 | flutter_lints: 58 | dependency: "direct dev" 59 | description: 60 | name: flutter_lints 61 | sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493 62 | url: "https://pub.dev" 63 | source: hosted 64 | version: "1.0.4" 65 | flutter_test: 66 | dependency: "direct dev" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | leak_tracker: 71 | dependency: transitive 72 | description: 73 | name: leak_tracker 74 | sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" 75 | url: "https://pub.dev" 76 | source: hosted 77 | version: "10.0.4" 78 | leak_tracker_flutter_testing: 79 | dependency: transitive 80 | description: 81 | name: leak_tracker_flutter_testing 82 | sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" 83 | url: "https://pub.dev" 84 | source: hosted 85 | version: "3.0.3" 86 | leak_tracker_testing: 87 | dependency: transitive 88 | description: 89 | name: leak_tracker_testing 90 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "3.0.1" 94 | lints: 95 | dependency: transitive 96 | description: 97 | name: lints 98 | sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c 99 | url: "https://pub.dev" 100 | source: hosted 101 | version: "1.0.1" 102 | matcher: 103 | dependency: transitive 104 | description: 105 | name: matcher 106 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 107 | url: "https://pub.dev" 108 | source: hosted 109 | version: "0.12.16+1" 110 | material_color_utilities: 111 | dependency: transitive 112 | description: 113 | name: material_color_utilities 114 | sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" 115 | url: "https://pub.dev" 116 | source: hosted 117 | version: "0.8.0" 118 | meta: 119 | dependency: transitive 120 | description: 121 | name: meta 122 | sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" 123 | url: "https://pub.dev" 124 | source: hosted 125 | version: "1.12.0" 126 | path: 127 | dependency: transitive 128 | description: 129 | name: path 130 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 131 | url: "https://pub.dev" 132 | source: hosted 133 | version: "1.9.0" 134 | sky_engine: 135 | dependency: transitive 136 | description: flutter 137 | source: sdk 138 | version: "0.0.99" 139 | source_span: 140 | dependency: transitive 141 | description: 142 | name: source_span 143 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "1.10.0" 147 | stack_trace: 148 | dependency: transitive 149 | description: 150 | name: stack_trace 151 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "1.11.1" 155 | stream_channel: 156 | dependency: transitive 157 | description: 158 | name: stream_channel 159 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "2.1.2" 163 | string_scanner: 164 | dependency: transitive 165 | description: 166 | name: string_scanner 167 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "1.2.0" 171 | term_glyph: 172 | dependency: transitive 173 | description: 174 | name: term_glyph 175 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "1.2.1" 179 | test_api: 180 | dependency: transitive 181 | description: 182 | name: test_api 183 | sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "0.7.0" 187 | vector_math: 188 | dependency: transitive 189 | description: 190 | name: vector_math 191 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 192 | url: "https://pub.dev" 193 | source: hosted 194 | version: "2.1.4" 195 | vm_service: 196 | dependency: transitive 197 | description: 198 | name: vm_service 199 | sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" 200 | url: "https://pub.dev" 201 | source: hosted 202 | version: "14.2.1" 203 | sdks: 204 | dart: ">=3.3.0 <4.0.0" 205 | flutter: ">=3.18.0-18.0.pre.54" 206 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: pal_widgets 2 | description: A set of amazing onboarding widgets for your flutter applications. 3 | version: 0.4.0 4 | repository: https://github.com/Apparence-io/pal-widgets 5 | 6 | environment: 7 | sdk: ">=2.15.0 <3.0.0" 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | collection: ^1.15.0 12 | flutter: 13 | sdk: flutter 14 | 15 | dev_dependencies: 16 | flutter_lints: ^1.0.0 17 | flutter_test: 18 | sdk: flutter 19 | 20 | # For information on the generic Dart part of this file, see the 21 | # following page: https://dart.dev/tools/pub/pubspec 22 | # The following section is specific to Flutter. 23 | flutter: null 24 | 25 | # To add assets to your package, add an assets section, like this: 26 | # assets: 27 | # - images/a_dot_burr.jpeg 28 | # - images/a_dot_ham.jpeg 29 | # 30 | # For details regarding assets in packages, see 31 | # https://flutter.dev/assets-and-images/#from-packages 32 | # 33 | # An image asset can refer to one or more resolution-specific "variants", see 34 | # https://flutter.dev/assets-and-images/#resolution-aware. 35 | # To add custom fonts to your package, add a fonts section here, 36 | # in this "flutter" section. Each entry in this list should have a 37 | # "family" key with the font family name, and a "fonts" key with a 38 | # list giving the asset and other descriptors for the font. For 39 | # example: 40 | # fonts: 41 | # - family: Schyler 42 | # fonts: 43 | # - asset: fonts/Schyler-Regular.ttf 44 | # - asset: fonts/Schyler-Italic.ttf 45 | # style: italic 46 | # - family: Trajan Pro 47 | # fonts: 48 | # - asset: fonts/TrajanPro.ttf 49 | # - asset: fonts/TrajanPro_Bold.ttf 50 | # weight: 700 51 | # 52 | # For details regarding fonts in packages, see 53 | # https://flutter.dev/custom-fonts/#from-packages 54 | -------------------------------------------------------------------------------- /test/anchored/anchored_circle_widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:pal_widgets/pal_widgets.dart'; 4 | 5 | import '../screen_variants.dart'; 6 | import 'widgets/anchored_circle_page_.dart'; 7 | import 'widgets/anchored_circle_no_buttons.dart'; 8 | 9 | void main() { 10 | group('circle anchor widget with buttons', () { 11 | final screenSizeVariants = ValueVariant(basicPhones); 12 | 13 | testWidgets('click on button => shows an anchored widget overlay', ( 14 | WidgetTester tester, 15 | ) async { 16 | await tester.setScreenSize(screenSizeVariants.currentValue!); 17 | //--- 18 | await tester.pumpWidget(const MyAppWithCircleAnchored()); 19 | expect(find.byType(AnchoredHelper), findsNothing); 20 | await tester.tap(find.byType(OutlinedButton).first); 21 | await tester.pump(const Duration(seconds: 2)); 22 | expect(find.byType(AnchoredHelper), findsOneWidget); 23 | }, variant: screenSizeVariants); 24 | 25 | testWidgets( 26 | 'shows an anchored widget overlay => positiv button close helper', ( 27 | WidgetTester tester, 28 | ) async { 29 | await tester.pumpWidget(const MyAppWithCircleAnchored()); 30 | await tester.tap(find.byType(OutlinedButton).first); 31 | await tester.pump(const Duration(seconds: 2)); 32 | // tap on positiv button 33 | final btn1 = find.byType(OutlinedButton).at(1).evaluate().first.widget 34 | as OutlinedButton; 35 | btn1.onPressed!(); 36 | await tester.pump(const Duration(seconds: 2)); 37 | // variants.currentValue; 38 | expect(find.byType(AnchoredHelper), findsNothing); 39 | }); 40 | 41 | testWidgets( 42 | 'shows an anchored widget overlay => negative button close helper', ( 43 | WidgetTester tester, 44 | ) async { 45 | await tester.pumpWidget(const MyAppWithCircleAnchored()); 46 | await tester.tap(find.byType(OutlinedButton).first); 47 | await tester.pump(const Duration(seconds: 2)); 48 | // tap on positiv button 49 | final btn2 = find.byType(OutlinedButton).at(2).evaluate().first.widget 50 | as OutlinedButton; 51 | btn2.onPressed!(); 52 | await tester.pump(const Duration(seconds: 2)); 53 | 54 | expect(find.byType(AnchoredHelper), findsNothing); 55 | }); 56 | 57 | testWidgets( 58 | '''shows an anchored widget overlay, anchorAction is null, tap on anchor 59 | => nothing happens''', 60 | ( 61 | WidgetTester tester, 62 | ) async { 63 | await tester.pumpWidget(const MyAppWithCircleAnchored()); 64 | await tester.tap(find.byType(OutlinedButton).first); 65 | await tester.pump(const Duration(seconds: 2)); 66 | // tap on anchor area button 67 | await tester.tap(find.byType(FloatingActionButton)); 68 | await tester.pump(const Duration(seconds: 2)); 69 | 70 | expect(find.byType(AnchoredHelper), findsOneWidget); 71 | }, 72 | ); 73 | }); 74 | 75 | group('Circle anchor widget - no buttons', () { 76 | testWidgets('click on button => shows an anchored widget overlay', ( 77 | WidgetTester tester, 78 | ) async { 79 | await tester.pumpWidget(const MyAppAnchoredNoBtn()); 80 | expect(find.byType(AnchoredHelper), findsNothing); 81 | await tester.tap(find.byType(OutlinedButton).first); 82 | await tester.pump(const Duration(seconds: 2)); 83 | expect(find.byType(AnchoredHelper), findsOneWidget); 84 | }); 85 | 86 | testWidgets( 87 | 'shows an anchored widget overlay => tap on anchor area close helper', ( 88 | WidgetTester tester, 89 | ) async { 90 | await tester.pumpWidget(const MyAppAnchoredNoBtn()); 91 | await tester.tap(find.byType(OutlinedButton).first); 92 | await tester.pump(const Duration(seconds: 2)); 93 | // tap on anchor area button 94 | await tester.tap(find.byType(FloatingActionButton)); 95 | await tester.pump(const Duration(seconds: 2)); 96 | 97 | expect(find.byType(AnchoredHelper), findsNothing); 98 | }); 99 | 100 | testWidgets( 101 | '''tap on anchor area => helper is closed and counter is not incremented 102 | (still 0)''', ( 103 | WidgetTester tester, 104 | ) async { 105 | await tester.pumpWidget(const MyAppAnchoredNoBtn()); 106 | await tester.tap(find.byType(OutlinedButton).first); 107 | await tester.pump(const Duration(seconds: 2)); 108 | // tap on anchor area button 109 | await tester.tap(find.byType(FloatingActionButton)); 110 | await tester.pump(const Duration(seconds: 2)); 111 | 112 | expect(find.byType(AnchoredHelper), findsNothing); 113 | expect(find.text('0'), findsOneWidget); 114 | }); 115 | }); 116 | } 117 | -------------------------------------------------------------------------------- /test/anchored/anchored_rect_widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:pal_widgets/pal_widgets.dart'; 4 | 5 | import 'widgets/anchored_rect_no_buttons.dart'; 6 | import 'widgets/anchored_rect_page.dart'; 7 | 8 | void main() { 9 | group('rect anchor widget with buttons', () { 10 | testWidgets('click on button => shows an anchored widget overlay', ( 11 | WidgetTester tester, 12 | ) async { 13 | await tester.pumpWidget(const MyAppWithRectAnchored()); 14 | expect(find.byType(AnchoredHelper), findsNothing); 15 | await tester.tap(find.byType(OutlinedButton).first); 16 | await tester.pump(const Duration(seconds: 2)); 17 | expect(find.byType(AnchoredHelper), findsOneWidget); 18 | }); 19 | 20 | testWidgets( 21 | 'shows an anchored widget overlay => positiv button close helper', ( 22 | WidgetTester tester, 23 | ) async { 24 | await tester.pumpWidget(const MyAppWithRectAnchored()); 25 | await tester.tap(find.byType(OutlinedButton).first); 26 | await tester.pump(const Duration(seconds: 2)); 27 | // tap on positiv button 28 | final btn1 = find.byType(OutlinedButton).at(1).evaluate().first.widget 29 | as OutlinedButton; 30 | btn1.onPressed!(); 31 | await tester.pump(const Duration(seconds: 2)); 32 | 33 | expect(find.byType(AnchoredHelper), findsNothing); 34 | }); 35 | 36 | testWidgets( 37 | 'shows an anchored widget overlay => negative button close helper', ( 38 | WidgetTester tester, 39 | ) async { 40 | await tester.pumpWidget(const MyAppWithRectAnchored()); 41 | await tester.tap(find.byType(OutlinedButton).first); 42 | await tester.pump(const Duration(seconds: 2)); 43 | // tap on positiv button 44 | final btn2 = find.byType(OutlinedButton).at(2).evaluate().first.widget 45 | as OutlinedButton; 46 | btn2.onPressed!(); 47 | await tester.pump(const Duration(seconds: 2)); 48 | 49 | expect(find.byType(AnchoredHelper), findsNothing); 50 | }); 51 | }); 52 | 53 | group('rect anchor widget - no buttons', () { 54 | testWidgets('click on button => shows an anchored widget overlay', ( 55 | WidgetTester tester, 56 | ) async { 57 | await tester.pumpWidget(const MyAppAnchoredNoBtn()); 58 | expect(find.byType(AnchoredHelper), findsNothing); 59 | await tester.tap(find.byType(OutlinedButton).first); 60 | await tester.pump(const Duration(seconds: 2)); 61 | expect(find.byType(AnchoredHelper), findsOneWidget); 62 | }); 63 | 64 | testWidgets( 65 | 'shows an anchored widget overlay => tap on anchor area close helper', ( 66 | WidgetTester tester, 67 | ) async { 68 | await tester.pumpWidget(const MyAppAnchoredNoBtn()); 69 | await tester.tap(find.byType(OutlinedButton).first); 70 | await tester.pump(const Duration(seconds: 2)); 71 | // tap on anchor area button 72 | await tester.tap(find.byType(FloatingActionButton)); 73 | await tester.pump(const Duration(seconds: 2)); 74 | 75 | expect(find.byType(AnchoredHelper), findsNothing); 76 | }); 77 | 78 | testWidgets( 79 | '''tap on anchor area => helper is closed and counter is not incremented 80 | (still 0)''', ( 81 | WidgetTester tester, 82 | ) async { 83 | await tester.pumpWidget(const MyAppAnchoredNoBtn()); 84 | await tester.tap(find.byType(OutlinedButton).first); 85 | await tester.pump(const Duration(seconds: 2)); 86 | // tap on anchor area button 87 | await tester.tap(find.byType(FloatingActionButton)); 88 | await tester.pump(const Duration(seconds: 2)); 89 | 90 | expect(find.byType(AnchoredHelper), findsNothing); 91 | expect(find.text('0'), findsOneWidget); 92 | }); 93 | }); 94 | } 95 | -------------------------------------------------------------------------------- /test/anchored/widgets/anchored_circle_no_buttons.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:pal_widgets/pal_widgets.dart'; 3 | 4 | class MyAppAnchoredNoBtn extends StatelessWidget { 5 | const MyAppAnchoredNoBtn({Key? key}) : super(key: key); 6 | 7 | // This widget is the root of your application. 8 | @override 9 | Widget build(BuildContext context) { 10 | return MaterialApp( 11 | title: 'Flutter Demo', 12 | theme: ThemeData( 13 | primarySwatch: Colors.blue, 14 | ), 15 | home: const HelperOrchestrator( 16 | child: MyHomePage2(title: 'Flutter Demo Home Page'), 17 | ), 18 | ); 19 | } 20 | } 21 | 22 | class MyHomePage2 extends StatefulWidget { 23 | const MyHomePage2({Key? key, required this.title}) : super(key: key); 24 | 25 | final String title; 26 | 27 | @override 28 | State createState() => _MyHomePage2State(); 29 | } 30 | 31 | class _MyHomePage2State extends State { 32 | int _counter = 0; 33 | 34 | void _incrementCounter() { 35 | setState(() { 36 | _counter++; 37 | }); 38 | } 39 | 40 | ButtonStyle get helperOutlineBtnStyle => OutlinedButton.styleFrom( 41 | padding: const EdgeInsets.all(12), 42 | textStyle: const TextStyle( 43 | color: Colors.white, 44 | fontSize: 18, 45 | ), 46 | side: const BorderSide( 47 | width: 1.0, 48 | style: BorderStyle.solid, 49 | color: Colors.white, 50 | ), 51 | shape: RoundedRectangleBorder( 52 | borderRadius: BorderRadius.circular(8.0), 53 | ), 54 | ); 55 | 56 | AnchoredHelper get helper => AnchoredHelper( 57 | title: const Text( 58 | 'Title lorem pitume', 59 | textAlign: TextAlign.center, 60 | style: TextStyle( 61 | color: Colors.white, 62 | fontSize: 32, 63 | ), 64 | ), 65 | description: const Text( 66 | 'Lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum', 67 | textAlign: TextAlign.center, 68 | style: TextStyle( 69 | color: Colors.white, 70 | fontSize: 21, 71 | ), 72 | ), 73 | bgColor: Colors.blue, 74 | onTapAnchor: () => HelperOrchestrator.of(context).hideHelper(), 75 | rightBtnStyle: helperOutlineBtnStyle, 76 | leftBtnStyle: helperOutlineBtnStyle, 77 | ); 78 | 79 | @override 80 | Widget build(BuildContext context) { 81 | return content; 82 | } 83 | 84 | Widget get content => Scaffold( 85 | appBar: AppBar( 86 | title: Text(widget.title), 87 | ), 88 | body: Center( 89 | child: Column( 90 | mainAxisAlignment: MainAxisAlignment.center, 91 | children: [ 92 | const Text( 93 | 'You have pushed the button this many times:', 94 | ), 95 | Text( 96 | '$_counter', 97 | style: const TextStyle(color: Colors.blue, fontSize: 32), 98 | ), 99 | const Text('test widget helper'), 100 | const SizedBox(height: 21), 101 | OutlinedButton( 102 | onPressed: () { 103 | HelperOrchestrator.of(context) // 104 | .showAnchoredHelper('text1', helper); 105 | }, 106 | child: const Text('push me 2'), 107 | ), 108 | ], 109 | ), 110 | ), 111 | floatingActionButton: FloatingActionButton( 112 | key: HelperOrchestrator.of(context).generateKey('text1'), 113 | onPressed: _incrementCounter, 114 | tooltip: 'Increment', 115 | child: const Icon(Icons.add), 116 | ), 117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /test/anchored/widgets/anchored_circle_page_.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:pal_widgets/pal_widgets.dart'; 3 | 4 | class MyAppWithCircleAnchored extends StatelessWidget { 5 | const MyAppWithCircleAnchored({Key? key}) : super(key: key); 6 | 7 | // This widget is the root of your application. 8 | @override 9 | Widget build(BuildContext context) { 10 | return MaterialApp( 11 | title: 'Flutter Demo', 12 | theme: ThemeData( 13 | primarySwatch: Colors.blue, 14 | ), 15 | home: const HelperOrchestrator( 16 | child: MyHomePage(title: 'Flutter Demo Home Page'), 17 | ), 18 | ); 19 | } 20 | } 21 | 22 | class MyHomePage extends StatefulWidget { 23 | const MyHomePage({Key? key, required this.title}) : super(key: key); 24 | 25 | final String title; 26 | 27 | @override 28 | State createState() => _MyHomePageState(); 29 | } 30 | 31 | class _MyHomePageState extends State { 32 | int _counter = 0; 33 | 34 | void _incrementCounter() { 35 | setState(() { 36 | _counter++; 37 | }); 38 | } 39 | 40 | ButtonStyle get helperOutlineBtnStyle => OutlinedButton.styleFrom( 41 | padding: const EdgeInsets.all(12), 42 | textStyle: const TextStyle( 43 | color: Colors.white, 44 | fontSize: 18, 45 | ), 46 | side: const BorderSide( 47 | width: 1.0, 48 | style: BorderStyle.solid, 49 | color: Colors.white, 50 | ), 51 | shape: RoundedRectangleBorder( 52 | borderRadius: BorderRadius.circular(8.0), 53 | ), 54 | ); 55 | 56 | AnchoredHelper get helper => AnchoredHelper( 57 | title: const Text( 58 | 'Title lorem pitume', 59 | textAlign: TextAlign.center, 60 | style: TextStyle( 61 | color: Colors.white, 62 | fontSize: 32, 63 | ), 64 | ), 65 | description: const Text( 66 | 'Lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum', 67 | textAlign: TextAlign.center, 68 | style: TextStyle( 69 | color: Colors.white, 70 | fontSize: 21, 71 | ), 72 | ), 73 | bgColor: Colors.blue, 74 | leftBtnText: const Text('cancel'), 75 | rightBtnText: const Text('Ok, understood'), 76 | rightBtnStyle: helperOutlineBtnStyle, 77 | leftBtnStyle: helperOutlineBtnStyle, 78 | onRightTap: () => HelperOrchestrator.of(context).hideHelper(), 79 | onLeftBtnTap: () => HelperOrchestrator.of(context).hideHelper(), 80 | ); 81 | 82 | @override 83 | Widget build(BuildContext context) { 84 | return content; 85 | } 86 | 87 | Widget get content => Scaffold( 88 | appBar: AppBar( 89 | title: Text(widget.title), 90 | ), 91 | body: Center( 92 | child: Column( 93 | mainAxisAlignment: MainAxisAlignment.center, 94 | children: [ 95 | const Text( 96 | 'You have pushed the button this many times:', 97 | ), 98 | Text( 99 | '$_counter', 100 | key: HelperOrchestrator.of(context).generateKey('text1'), 101 | style: const TextStyle(color: Colors.blue, fontSize: 32), 102 | ), 103 | Text( 104 | 'test widget helper', 105 | key: HelperOrchestrator.of(context).generateKey('text2'), 106 | ), 107 | const SizedBox(height: 21), 108 | OutlinedButton( 109 | onPressed: () { 110 | HelperOrchestrator.of(context) 111 | .showAnchoredHelper('text1', helper); 112 | }, 113 | child: const Text('push me 2'), 114 | ), 115 | ], 116 | ), 117 | ), 118 | floatingActionButton: FloatingActionButton( 119 | onPressed: _incrementCounter, 120 | tooltip: 'Increment', 121 | child: const Icon(Icons.add), 122 | ), 123 | ); 124 | } 125 | -------------------------------------------------------------------------------- /test/anchored/widgets/anchored_rect_no_buttons.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:pal_widgets/pal_widgets.dart'; 3 | 4 | class MyAppAnchoredNoBtn extends StatelessWidget { 5 | const MyAppAnchoredNoBtn({Key? key}) : super(key: key); 6 | 7 | // This widget is the root of your application. 8 | @override 9 | Widget build(BuildContext context) { 10 | return MaterialApp( 11 | title: 'Flutter Demo', 12 | theme: ThemeData( 13 | primarySwatch: Colors.blue, 14 | ), 15 | home: const HelperOrchestrator( 16 | child: MyHomePage2(title: 'Flutter Demo Home Page'), 17 | ), 18 | ); 19 | } 20 | } 21 | 22 | class MyHomePage2 extends StatefulWidget { 23 | const MyHomePage2({Key? key, required this.title}) : super(key: key); 24 | 25 | final String title; 26 | 27 | @override 28 | State createState() => _MyHomePage2State(); 29 | } 30 | 31 | class _MyHomePage2State extends State { 32 | int _counter = 0; 33 | 34 | void _incrementCounter() { 35 | setState(() { 36 | _counter++; 37 | }); 38 | } 39 | 40 | ButtonStyle get helperOutlineBtnStyle => OutlinedButton.styleFrom( 41 | padding: const EdgeInsets.all(12), 42 | textStyle: const TextStyle( 43 | color: Colors.white, 44 | fontSize: 18, 45 | ), 46 | side: const BorderSide( 47 | width: 1.0, 48 | style: BorderStyle.solid, 49 | color: Colors.white, 50 | ), 51 | shape: RoundedRectangleBorder( 52 | borderRadius: BorderRadius.circular(8.0), 53 | ), 54 | ); 55 | 56 | AnchoredHelper get helper => AnchoredHelper( 57 | title: const Text( 58 | 'Title lorem pitume', 59 | textAlign: TextAlign.center, 60 | style: TextStyle( 61 | color: Colors.white, 62 | fontSize: 32, 63 | ), 64 | ), 65 | description: const Text( 66 | 'Lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum', 67 | textAlign: TextAlign.center, 68 | style: TextStyle( 69 | color: Colors.white, 70 | fontSize: 21, 71 | ), 72 | ), 73 | bgColor: Colors.blue, 74 | onTapAnchor: () => HelperOrchestrator.of(context).hideHelper(), 75 | rightBtnStyle: helperOutlineBtnStyle, 76 | leftBtnStyle: helperOutlineBtnStyle, 77 | widgetFactory: AnchoredRectHoleHelper.anchorFactory, 78 | ); 79 | 80 | @override 81 | Widget build(BuildContext context) { 82 | return content; 83 | } 84 | 85 | Widget get content => Scaffold( 86 | appBar: AppBar( 87 | title: Text(widget.title), 88 | ), 89 | body: Center( 90 | child: Column( 91 | mainAxisAlignment: MainAxisAlignment.center, 92 | children: [ 93 | const Text( 94 | 'You have pushed the button this many times:', 95 | ), 96 | Text( 97 | '$_counter', 98 | style: const TextStyle(color: Colors.blue, fontSize: 32), 99 | ), 100 | const Text('test widget helper'), 101 | const SizedBox(height: 21), 102 | OutlinedButton( 103 | onPressed: () { 104 | HelperOrchestrator.of(context) // 105 | .showAnchoredHelper('text1', helper); 106 | }, 107 | child: const Text('push me 2'), 108 | ), 109 | ], 110 | ), 111 | ), 112 | floatingActionButton: FloatingActionButton( 113 | key: HelperOrchestrator.of(context).generateKey('text1'), 114 | onPressed: _incrementCounter, 115 | tooltip: 'Increment', 116 | child: const Icon(Icons.add), 117 | ), 118 | ); 119 | } 120 | -------------------------------------------------------------------------------- /test/anchored/widgets/anchored_rect_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:pal_widgets/pal_widgets.dart'; 3 | 4 | class MyAppWithRectAnchored extends StatelessWidget { 5 | const MyAppWithRectAnchored({Key? key}) : super(key: key); 6 | 7 | // This widget is the root of your application. 8 | @override 9 | Widget build(BuildContext context) { 10 | return MaterialApp( 11 | title: 'Flutter Demo', 12 | theme: ThemeData( 13 | primarySwatch: Colors.blue, 14 | ), 15 | home: const HelperOrchestrator( 16 | child: MyHomePage(title: 'Flutter Demo Home Page'), 17 | ), 18 | ); 19 | } 20 | } 21 | 22 | class MyHomePage extends StatefulWidget { 23 | const MyHomePage({Key? key, required this.title}) : super(key: key); 24 | 25 | final String title; 26 | 27 | @override 28 | State createState() => _MyHomePageState(); 29 | } 30 | 31 | class _MyHomePageState extends State { 32 | int _counter = 0; 33 | 34 | void _incrementCounter() { 35 | setState(() { 36 | _counter++; 37 | }); 38 | } 39 | 40 | ButtonStyle get helperOutlineBtnStyle => OutlinedButton.styleFrom( 41 | padding: const EdgeInsets.all(12), 42 | textStyle: const TextStyle( 43 | color: Colors.white, 44 | fontSize: 18, 45 | ), 46 | side: const BorderSide( 47 | width: 1.0, 48 | style: BorderStyle.solid, 49 | color: Colors.white, 50 | ), 51 | shape: RoundedRectangleBorder( 52 | borderRadius: BorderRadius.circular(8.0), 53 | ), 54 | ); 55 | 56 | AnchoredHelper get helper => AnchoredHelper( 57 | title: const Text( 58 | 'Title lorem pitume', 59 | textAlign: TextAlign.center, 60 | style: TextStyle( 61 | color: Colors.white, 62 | fontSize: 32, 63 | ), 64 | ), 65 | description: const Text( 66 | 'Lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum', 67 | textAlign: TextAlign.center, 68 | style: TextStyle( 69 | color: Colors.white, 70 | fontSize: 21, 71 | ), 72 | ), 73 | bgColor: Colors.blue, 74 | leftBtnText: const Text('cancel'), 75 | rightBtnText: const Text('Ok, understood'), 76 | rightBtnStyle: helperOutlineBtnStyle, 77 | leftBtnStyle: helperOutlineBtnStyle, 78 | onRightTap: () => HelperOrchestrator.of(context).hideHelper(), 79 | onLeftBtnTap: () => HelperOrchestrator.of(context).hideHelper(), 80 | widgetFactory: AnchoredRectHoleHelper.anchorFactory, 81 | ); 82 | 83 | @override 84 | Widget build(BuildContext context) { 85 | return content; 86 | } 87 | 88 | Widget get content => Scaffold( 89 | appBar: AppBar( 90 | title: Text(widget.title), 91 | ), 92 | body: Center( 93 | child: Column( 94 | mainAxisAlignment: MainAxisAlignment.center, 95 | children: [ 96 | const Text( 97 | 'You have pushed the button this many times:', 98 | ), 99 | Text( 100 | '$_counter', 101 | key: HelperOrchestrator.of(context).generateKey('text1'), 102 | style: const TextStyle(color: Colors.blue, fontSize: 32), 103 | ), 104 | Text( 105 | 'test widget helper', 106 | key: HelperOrchestrator.of(context).generateKey('text2'), 107 | ), 108 | const SizedBox(height: 21), 109 | OutlinedButton( 110 | onPressed: () { 111 | HelperOrchestrator.of(context) 112 | .showAnchoredHelper('text1', helper); 113 | }, 114 | child: const Text('push me 2'), 115 | ), 116 | ], 117 | ), 118 | ), 119 | floatingActionButton: FloatingActionButton( 120 | onPressed: _incrementCounter, 121 | tooltip: 'Increment', 122 | child: const Icon(Icons.add), 123 | ), 124 | ); 125 | } 126 | -------------------------------------------------------------------------------- /test/screen_variants.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | const iphone8Plus = ScreenSize('Iphone 8 Plus', 414, 736, 3); 6 | const iphone11Max = ScreenSize('Iphone 11 Max', 414, 896, 3); 7 | const samsungGalaxyS = ScreenSize('Android Samsung Galaxy S', 480, 800, 1); 8 | 9 | final basicPhones = {iphone8Plus, iphone11Max, samsungGalaxyS}; 10 | 11 | /// this class defines a size configuration of a specific device 12 | class ScreenSize { 13 | /// name this configuration 14 | final String name; 15 | 16 | /// size configuration and pixel density 17 | final double width, height, pixelDensity; 18 | 19 | const ScreenSize(this.name, this.width, this.height, this.pixelDensity); 20 | 21 | @override 22 | String toString() => name; 23 | } 24 | 25 | extension ScreenSizeManager on WidgetTester { 26 | Future setScreenSize(ScreenSize screenSize) async { 27 | return _setScreenSize( 28 | width: screenSize.width, 29 | height: screenSize.height, 30 | pixelDensity: screenSize.pixelDensity); 31 | } 32 | 33 | Future _setScreenSize( 34 | {double width = 540, 35 | double height = 960, 36 | double pixelDensity = 1}) async { 37 | final size = Size(width, height); 38 | await binding.setSurfaceSize(size); 39 | view.physicalSize = size; 40 | view.devicePixelRatio = pixelDensity; 41 | // Old code 42 | // binding.window.physicalSizeTestValue = size; 43 | // binding.window.devicePixelRatioTestValue = pixelDensity; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/services/element_finder_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:pal_widgets/services/element_finder.dart'; 4 | 5 | void main() { 6 | group('Element finder with String key', () { 7 | Widget page = Scaffold( 8 | body: Column( 9 | children: [ 10 | const Text("Test Text", key: ValueKey("text1")), 11 | const Text("Test Text 2", key: ValueKey("text2")), 12 | const Text("Test Text 3", key: ValueKey("text3")), 13 | // ignore: sized_box_for_whitespace 14 | Container(key: const ValueKey("container"), height: 50, width: 150) 15 | ], 16 | ), 17 | ); 18 | 19 | testWidgets(''' 20 | search a widget by key as String and find location + size. 21 | this only work if this is a valueKey 22 | ''', ( 23 | WidgetTester tester, 24 | ) async { 25 | BuildContext? _context; 26 | var app = MediaQuery( 27 | data: const MediaQueryData(), 28 | child: MaterialApp(home: Builder( 29 | builder: (context) { 30 | _context = context; 31 | return page; 32 | }, 33 | ))); 34 | await tester.pumpWidget(app); 35 | ElementFinder finder = ElementFinder(buildContext: _context); 36 | var result = finder.searchChildElement("container")!; 37 | expect(result, isNotNull); 38 | expect(result.bounds!.size, equals(const Size(150, 50))); 39 | }); 40 | 41 | testWidgets('scan widgets finds all with their rect', 42 | (WidgetTester tester) async { 43 | BuildContext? _context; 44 | var app = MediaQuery( 45 | data: const MediaQueryData(), 46 | child: MaterialApp(home: Builder( 47 | builder: (context) { 48 | _context = context; 49 | return page; 50 | }, 51 | ))); 52 | await tester.pumpWidget(app); 53 | ElementFinder finder = ElementFinder(buildContext: _context); 54 | var elements = finder.scan(); 55 | expect(elements, isNotNull); 56 | expect(elements.length, equals(6)); 57 | var keys = elements.keys.toList(); 58 | expect(keys[1], contains("text1")); 59 | expect(keys[2], contains("text2")); 60 | // expect(elements[keys.last].bounds.size, equals(Size(150, 50))); 61 | }); 62 | }); 63 | 64 | group('Element finder with key', () { 65 | final containerKey = UniqueKey(); 66 | 67 | Widget page = Scaffold( 68 | body: Column( 69 | children: [ 70 | const Text("Test Text", key: ValueKey("text1")), 71 | const Text("Test Text 2", key: ValueKey("text2")), 72 | const Text("Test Text 3", key: ValueKey("text3")), 73 | // ignore: sized_box_for_whitespace 74 | Container(key: containerKey, height: 50, width: 150) 75 | ], 76 | ), 77 | ); 78 | 79 | testWidgets('search a widget by key and find location + size', ( 80 | WidgetTester tester, 81 | ) async { 82 | BuildContext? _context; 83 | var app = MediaQuery( 84 | data: const MediaQueryData(), 85 | child: MaterialApp(home: Builder( 86 | builder: (context) { 87 | _context = context; 88 | return page; 89 | }, 90 | ))); 91 | await tester.pumpWidget(app); 92 | ElementFinder finder = ElementFinder(buildContext: _context); 93 | var result = 94 | finder.searchChildElementByKey(containerKey, isInModal: false)!; 95 | expect(result, isNotNull); 96 | expect(result.bounds!.size, equals(const Size(150, 50))); 97 | }); 98 | }); 99 | } 100 | --------------------------------------------------------------------------------