diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..d1f56039f Binary files /dev/null and b/.DS_Store differ diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..97dab5fa0 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: [filledstacks] +custom: [https://dane-mackier-s-school.teachable.com/p/master-flutter-on-the-web] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..e34e18fb1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,30 @@ +name: Release Package + +on: + push: + branches: + - master + - next + +jobs: + package_and_publish: + name: Package and Publish + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.REPO_DEPLOYMENT_TOKEN }} + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + - run: flutter pub get + - name: release + uses: cycjimmy/semantic-release-action@v3 + with: + extra_plugins: | + @semantic-release/exec + @semantic-release/git + @semantic-release/changelog + env: + GITHUB_TOKEN: ${{ secrets.REPO_DEPLOYMENT_TOKEN }} + CREDENTIALS: ${{ secrets.PUB_DEV_DEPLOYMENT_CREDENTIALS }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..05cb91792 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,32 @@ +name: Test Package + +on: + pull_request: + branches: + - '**' + workflow_dispatch: + +jobs: + lint_and_test: + name: Linting and Testing + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + - run: flutter pub get + - run: dart format --fix --set-exit-if-changed . + # - run: flutter analyze # removing analyze for now, we only care about test passing + - run: flutter test --coverage + - name: Setup LCOV + uses: hrishikesh-kadam/setup-lcov@v1 + - name: Report code coverage + uses: zgosalvez/github-actions-report-lcov@v4 + with: + coverage-files: coverage/lcov*.info + artifact-name: code-coverage-report + github-token: ${{ secrets.GITHUB_TOKEN }} + update-comment: true diff --git a/.gitignore b/.gitignore index 15392b106..d2902c7cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,81 @@ -.project -org.eclipse.buildship.core.prefs -.classpath -launch.json +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ -**/*.lock -*.lock \ No newline at end of file +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ +pubspec.lock + +# Dependency releated +pubspec_overrides.yaml + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +*.lock +.DS_Store diff --git a/packages/stacked/.metadata b/.metadata similarity index 100% rename from packages/stacked/.metadata rename to .metadata diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 000000000..617e9a846 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json.schemastore.org/semantic-release", + "branches": [ + "master", + { + "name": "next", + "prerelease": "pre" + } + ], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/changelog", + [ + "@semantic-release/exec", + { + "verifyConditionsCmd": "if [ -z \"$CREDENTIALS\" ]; then exit 1; fi && mkdir -p ~/.config/dart && echo \"$CREDENTIALS\" > ~/.config/dart/pub-credentials.json", + "prepareCmd": "sed -i 's/^version: .*$/version: ${nextRelease.version}/' pubspec.yaml", + "publishCmd": "flutter pub publish -f" + } + ], + [ + "@semantic-release/git", + { + "assets": ["./CHANGELOG.md", "./pubspec.yaml"] + } + ], + "@semantic-release/github" + ] +} diff --git a/packages/stacked/CHANGELOG.md b/CHANGELOG.md similarity index 55% rename from packages/stacked/CHANGELOG.md rename to CHANGELOG.md index 589d47916..fe7584d0d 100644 --- a/packages/stacked/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,235 @@ +## [3.4.4](https://github.com/Stacked-Org/stacked/compare/v3.4.3...v3.4.4) (2024-12-12) + + +### Bug Fixes + +* **deps:** update dependency get_it to v8 ([#1128](https://github.com/Stacked-Org/stacked/issues/1128)) ([4398fed](https://github.com/Stacked-Org/stacked/commit/4398fed7cef851d11223cf93d6858ef34e84ac76)) + +## [3.4.3](https://github.com/Stacked-Org/stacked/compare/v3.4.2...v3.4.3) (2024-06-24) + + +### Bug Fixes + +* adds corner radius to SkeletonLoader ([1b2c51f](https://github.com/Stacked-Org/stacked/commit/1b2c51fd1f91ae3fd32626afcceaa27c582d7b5b)) + +## [3.4.2](https://github.com/Stacked-Org/stacked/compare/v3.4.1...v3.4.2) (2024-01-30) + + +### Bug Fixes + +* maintenance update ([399403d](https://github.com/Stacked-Org/stacked/commit/399403dba0760abe0ce7bb83b43b339064dee2e6)) + +## [3.4.1](https://github.com/Stacked-Org/stacked/compare/v3.4.0...v3.4.1) (2023-06-22) + + +### Bug Fixes + +* expose setCurrentWebPageIndex on IndexTrackingStateHelper ([#976](https://github.com/Stacked-Org/stacked/issues/976)) ([2057589](https://github.com/Stacked-Org/stacked/commit/2057589729f8864afba930b61443be7adb966f34)), closes [#973](https://github.com/Stacked-Org/stacked/issues/973) [#967](https://github.com/Stacked-Org/stacked/issues/967) + +# [3.4.0](https://github.com/Stacked-Org/stacked/compare/v3.3.0...v3.4.0) (2023-05-31) + + +### Features + +* set currentIndex from route on Web Platform ([#963](https://github.com/Stacked-Org/stacked/issues/963)) ([cfd1378](https://github.com/Stacked-Org/stacked/commit/cfd1378b098db100668b491ba01f6c00f0a8701d)) + +# [3.3.0](https://github.com/Stacked-Org/stacked/compare/v3.2.8...v3.3.0) (2023-05-26) + + +### Features + +* Add onAllFuturesComplete to MultipleFutureViewModel ([#958](https://github.com/Stacked-Org/stacked/issues/958)) ([9d3fc4f](https://github.com/Stacked-Org/stacked/commit/9d3fc4f809a6123a03362003db79551274e161ff)) + +## [3.2.8](https://github.com/Stacked-Org/stacked/compare/v3.2.7...v3.2.8) (2023-05-23) + + +### Bug Fixes + +* Updates Stacked team's maintenance schedule ([3542c08](https://github.com/Stacked-Org/stacked/commit/3542c0814c539c9b5de16dd6f9f2bdc39bf81a17)) + +## [3.2.7](https://github.com/Stacked-Org/stacked/compare/v3.2.6...v3.2.7) (2023-05-17) + + +### Bug Fixes + +* Update link for course ([cf585d5](https://github.com/Stacked-Org/stacked/commit/cf585d50b690d82ecbb35e991d8c430b63a4adf0)) + +## [3.2.6](https://github.com/Stacked-Org/stacked/compare/v3.2.5...v3.2.6) (2023-05-16) + + +### Bug Fixes + +* force rebuild ViewModelwidget to follow natural flutter build path ([f794d0c](https://github.com/Stacked-Org/stacked/commit/f794d0c2813943060e885d0946e66a251784e5f2)) + +## [3.2.5](https://github.com/Stacked-Org/stacked/compare/v3.2.4...v3.2.5) (2023-05-11) + + +### Bug Fixes + +* Removes funding link from pubspec ([a1530f0](https://github.com/Stacked-Org/stacked/commit/a1530f08e2f8079aff25ca54b751fa42221663c7)) + +## [3.2.4](https://github.com/Stacked-Org/stacked/compare/v3.2.3...v3.2.4) (2023-05-11) + + +### Bug Fixes + +* updating deprecated members to support +3.10 ([0004072](https://github.com/Stacked-Org/stacked/commit/00040728148867dec8baf9b04d7912db4e68ce8d)) + +## [3.2.3](https://github.com/Stacked-Org/stacked/compare/v3.2.2...v3.2.3) (2023-04-30) + + +### Bug Fixes + +* Updates readme banner to show pre-sale Flutter Course release ([79b9cf6](https://github.com/Stacked-Org/stacked/commit/79b9cf6b0b61fe3b191ec61fdbd7bd76b0b48cc9)) + +## [3.2.2](https://github.com/Stacked-Org/stacked/compare/v3.2.1...v3.2.2) (2023-04-25) + + +### Bug Fixes + +* Adds new course to readme ([1510680](https://github.com/Stacked-Org/stacked/commit/1510680c118cd91fd67eeaf046fa0ec649f16e16)) + +## [3.2.1](https://github.com/Stacked-Org/stacked/compare/v3.2.0...v3.2.1) (2023-04-12) + + +### Bug Fixes + +* Updates shared version ([c0d24d8](https://github.com/Stacked-Org/stacked/commit/c0d24d8c4220467e5e52a45282279b3c8e44846a)) + +# [3.2.0](https://github.com/Stacked-Org/stacked/compare/v3.1.2...v3.2.0) (2023-03-15) + + +### Features + +* Merge latest version from beta ([#907](https://github.com/Stacked-Org/stacked/issues/907)) ([4e519aa](https://github.com/Stacked-Org/stacked/commit/4e519aa77d7cda989b5716ceec7d54ad0f3e01a0)) + +## 3.2.0 + +### Features + +Adds support for the new RouterService which uses Navigator 2.0 a fork from AutoRouter `5.0.4` to generate the generated code. + +### Fixes +- Updates `stacked_core` to `stacked_shared`. We lost access to `stacked_core` 😭 + +## 3.1.1 +- Added fix to extensible classes for ListenableServiceMixin + +## 3.1.0+3 +- Updates promotional banner + +## 3.1.0+2 +- Fixes old doc link + +## 3.1.0+1 + +- Updates banner correctly + +## 3.1.0 + +### Features +New CLI πŸ”₯ Check it out in the [Docs](https://stacked.filledstacks.com) + +- Fixes the listenable and reactive service late initialisation error +- Deprecates `ReactiveServiceMixin` +- Deprecates `ViewModelBuilderWidget` in favour of `StackedView` +- Deprecates `HookViewModelWidget` in favour of `StackedHookView` +- Deprecates `fireOnModelReadyOnce` in favour of `fireOnViewModelReadyOnce` +- Deprecates `onModelReady` in favour of `onViewModelReady` +- Adds `listenersCount`Β to `ListenableServiceMixin` +- Updates ViewModels to use `viewModel` instead of `model` +- Makes setFormStatus as NOT required override +- Adds a better named `rebuildUi` function to replace `notifyListeners` +- Renames ViewModelBuilderWidget to StackedView +- Updates BuilderWidgetExampleView + +## 3.0.1 + +- Add onDispose method to ViewModelBuilderWidget + +## 3.0.0+2 + +- Fix [#767](https://github.com/FilledStacks/stacked/issues/767) + +## 3.0.0+1 + +- Replace the old `ReactiveValues` approach with `notifyListeners()` when using the `ReactiveServiceMixin` in readme + +## 3.0.0 + +- Passes generic argument to `DataStateHelper` +- Uses `FormStateHelper` for the `FormViewModel` +- Adds `BusyAndErrorStateHelper` to the `BaseViewModel` +- Refactor `BaseViewModel` by extracting the different functionalities to mixins. +- Remove the inconsistency between the `BaseViewModel` and `FutureViewModel`, `StreamViewModel` when setting the error, data values. +- Now we have three mixins that can be mix with any viewmodel and they are: + - `FutureRunnerHelper` that has `runBusyFuture` and `runErrorFuture` functions. + - `MessageStateHelper` that adds message property to the viewmodel. + - `DataStateHelper` that adds data property to the viewmodel. + - `FormStateHelper` that gives the flexibility to mixin the form functionality with the any other view model. + - `IndexTrackingStateHelper` that give the flexibility to mixin the index tracking in a bottom navigation bar any other view model. + +## 2.3.15 + +- Rename `SelectorViewModelBuilderWidget` and `SelectorViewModelWidget` similar to `ViewModelWidget` + +## 2.3.14 + +- Adds `SelectorViewModelBuilder` and `SelectorViewModelBuilderWidget` + +## 2.3.13 + +- Adds Message state functionality for view models + +## 2.3.12 + +- Fix [#486](https://github.com/FilledStacks/stacked/issues/486) + +## 2.3.11 + +- Fix [#546](https://github.com/FilledStacks/stacked/issues/546) + +## 2.3.10 + +- Fixes issues on navigation + +## 2.3.9 + +- Pump stacked_shared version +- Update example app + +## 2.3.8 + +- Remove duplicate code and Clean up + +## 2.3.7 + +### Flutter v3 compatibility + +- Use [ambiguate](https://docs.flutter.dev/development/tools/sdk/release-notes/release-notes-3.0.0#your-code) to ensure v3 (and backward) compatibility + +### Fixes + +- Add move in left route transition + +## 2.3.6 + +- Reverts 2.3.4 + +## 2.3.5 + +- Use the stacked_shared package to expose the stacked_annotation + +## 2.3.4 + +- Updates MultipleStreamViewModel to take generic type for key + +## 2.3.3+1 + +- Updates Example's kotlin version to 1.4.10 +- Updates Example's Android Embedding to Version 2 +- Updates Example's CompileSdk to 31 + ## 2.3.3 - Returns a null value for catchError handler when using FutureViewModel diff --git a/packages/stacked/LICENSE b/LICENSE similarity index 100% rename from packages/stacked/LICENSE rename to LICENSE diff --git a/README.md b/README.md index 872b67987..b94f5cf62 100644 --- a/README.md +++ b/README.md @@ -1,157 +1,21 @@ -# Stacked +# Stacked [![Pub Version](https://img.shields.io/pub/v/stacked)](https://pub.dev/packages/stacked) -An architecture developed and revised by the [FilledStacks](https://www.youtube.com/filledstacks) community. This architecture was initially a version of MVVM as [described in this video](https://youtu.be/kDEflMYTFlk). Since then Filledstacks app development team has built 6 production applications with various requirements. This experience along with countless requests for improvements and common functionality is what sparked the creation of this architecture package. It aims to provide **common functionalities to make app development easier** as well as code principles to use during development to ensure your code stays maintainable. +## [Checkout out the new and improved Docs](https://stacked.filledstacks.com/) -**If you're reading this disclaimer, the series that does a deep-dive on this architecture has not been published yet.** +Old doc can be found [here](https://github.com/Stacked-Org/stacked/blob/master/README_old.md) -## How it works +# What is Stacked -The architecture is very simple. It consists of 3 major pieces, everything else is up to your implementation style. These pieces are: +Stacked is a Flutter Framework for building Production Applications. It is a complete frontend architecture for Flutter. The framework is created to build testable and maintainable code. -- **View**: Shows the UI to the user. Single widgets also qualify as views (for consistency in terminology) a view, in this case, is not a "Page" it's just a UI representation. -- **ViewModel**: Manages the state of the View, business logic, and any other logic as required from user interaction. It does this by making use of the services -- **Services**: A wrapper of a single functionality/feature set. This is commonly used to wrap things like showing a dialog, wrapping database functionality, integrating an API, etc. +Checkout the [official docs](https://stacked.filledstacks.com/) for more information. -Let's go over some of those principles to follow during development. +--- -- Views should never MAKE USE of a service directly. -- Views should only contain logic if necessary. If the logic is from UI only items then we do the least amount of required logic and pass the rest to the ViewModel. -- Views should ONLY render the state in its ViewModel. -- ViewModels for widgets that represent page views are bound to a single View only. -- ViewModels may be re-used if the UI requires the same functionality. -- ViewModels should not know about other ViewModels. +## Maintenance Schedule -That's quite a bit of "rules", but they help during production. Trust me. +This schedule indicates when work will be done on Stacked. Work includes fixing issues, building new features, improving the code stability or implementing automation. If there is no urgent breaking bug you can expect responses to your issues on the days listed below from one of the maintainers. -## Stacked's place in your architecture +- [Fernando](https://github.com/ferrarafer): Monday -Stacked provides you with classes and functionalities to make it easy to implement that base architecture that this package is built for. There are additional things that you can add to your application that will make the usage of this architecture much more pleasant. This will be discussed in full on the architecture series that will come out soon. Everything from navigation, dependency injection, service location, error handling, etc. -## Packages - -In the effort of providing as much value with the stacked package as possible, the repo contains all of the other packages that extend the stacked functionality further and implements some of the base functionalities for you. It also contains third-party extensions that can be used with stacked. - -| Package | Pub | -| -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -| [stacked](https://github.com/FilledStacks/stacked/tree/master/packages/stacked) | [![pub package](https://img.shields.io/pub/v/stacked.svg)](https://pub.dev/packages/stacked) | -| [stacked_generator](https://github.com/FilledStacks/stacked/tree/master/packages/stacked_generator) | [![pub package](https://img.shields.io/pub/v/stacked_generator.svg)](https://pub.dev/packages/stacked_generator) | -| [stacked_services](https://github.com/FilledStacks/stacked/tree/master/packages/stacked_services) | [![pub package](https://img.shields.io/pub/v/stacked_services.svg)](https://pub.dev/packages/stacked_services) | -| [stacked_hooks](https://github.com/FilledStacks/stacked/tree/master/packages/stacked_hooks) | [![pub package](https://img.shields.io/pub/v/stacked_hooks.svg)](https://pub.dev/packages/stacked_hooks) | -| [stacked_themes](https://github.com/FilledStacks/stacked/tree/master/packages/stacked_themes) | [![pub package](https://img.shields.io/pub/v/stacked_themes.svg)](https://pub.dev/packages/stacked_themes) | -| [stacked_crashlytics](https://github.com/FilledStacks/stacked/tree/master/packages/stacked_crashlytics) | [![pub package](https://img.shields.io/pub/v/stacked_crashlytics.svg)](https://pub.dev/packages/stacked_crashlytics) | -| [stacked_firebase_auth](https://github.com/FilledStacks/stacked/tree/master/packages/stacked_firebase_auth) | [![pub package](https://img.shields.io/pub/v/stacked_firebase_auth.svg)](https://pub.dev/packages/stacked_firebase_auth) | -| [stacked_localisation](https://github.com/FilledStacks/stacked/tree/master/packages/stacked_localisation/stacked_localisation) | [![pub package](https://img.shields.io/pub/v/stacked_localisation.svg)](https://pub.dev/packages/stacked_localisation) | -| [stacked_localisation_generator](https://github.com/FilledStacks/stacked/tree/master/packages/stacked_localisation/stacked_localisation_generator) | [![pub package](https://img.shields.io/pub/v/stacked_localisation_generator.svg)](https://pub.dev/packages/stacked_localisation_generator) | - -Each package folder contains instructions on how to use the package so please look at the README per package for detailed examples. - -## Migrating from `provider_architecture` to Stacked - -Let's start with a statement to ease your migration panic πŸ˜… stacked is the same code from `provider_architecture` with name changes and removal of some old deprecated properties. If you don't believe me, open the repo's side by side and look at the lib folders. Well, up till yesterday (22 April 2020) I guess when I updated the BaseViewModel. I wanted to do this to show that stacked is production-ready from the go. It's a new package but it's been used by all of you and the FilledStacks development team for months in the form of `provider_architecture`. With that out of the way, let's start the migrate. - -### ViewModelProvider Migration - -This class has now been more appropriately named `ViewModelBuilder`. This is to match it's functionality more closely. Building UI FROM the ViewModel. The ViewModel is used to drive the state of the reactive UI. - -Migrations to take note of: - -- `ViewModelProvider` -> `ViewModelBuilder` -- Named constructor `withoutConsumer` is now called `nonReactive` -- Named constructor `withConsumer` is now called `reactive` -- Instead of passing a constructed `ViewModel` which was constructing every rebuilder we pass a `viewModelBuilder`. A function that returns a `ChangeNotifier`. -- `reuseExisting` has changed to `disposeViewModel` and now has a default value of true. If you used `reuseExisting=true` it has to change to `disposeViewModel=false`. - -Let's look at that in code. We'll go over `withoutConsumer/nonReactive` first - -```dart -class HomeViewMultipleWidgets extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ViewModelProvider.withoutConsumer( - viewModel: HomeViewModel(), - onModelReady: (model) => model.initialise(), - reuseExisting: true, - builder: (context, model, _) => Scaffold( - floatingActionButton: FloatingActionButton( - onPressed: () { - model.updateTitle(); - }, - ), - body: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [TitleSection(), DescriptionSection()], - ), - ), - ); - } -} -``` - -Will Change to - -```dart -class HomeViewMultipleWidgets extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ViewModelBuilder.nonReactive( // Take note here - viewModelBuilder: () => HomeViewModel(), // Take note here - disposeViewModel: false, // Take note here - onModelReady: (model) => model.initialise(), - builder: (context, model, _) => Scaffold( - floatingActionButton: FloatingActionButton( - onPressed: () { - model.updateTitle(); - }, - ), - body: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [TitleSection(), DescriptionSection()], - ), - ), - ); - } -} -``` - -For the `withConsumer` function we do the following - -```dart -class HomeView extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ViewModelProvider.withConsumer( - ); - } -} -``` - -Changes to - -```dart -class HomeView extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( // Take note here - - ); - } -} -``` - -### ProviderWidget Migration - -The only change here was the name. - -```dart -class DuplicateNameWidget extends ProviderWidget { - -} - -// Becomes - -class DuplicateNameWidget extends ViewModelWidget { - -} -``` - -The rest of the package is all new functionality which can be seen above. Please check out the issues for tasks we'd like to add. If you would like to see any functionality in here please create an issue and I'll assess and provide feedback. diff --git a/packages/stacked/README.md b/README_old.md similarity index 95% rename from packages/stacked/README.md rename to README_old.md index c4b0f4844..7fd98dadc 100644 --- a/packages/stacked/README.md +++ b/README_old.md @@ -201,6 +201,51 @@ class BuilderWidgetExampleView extends ViewModelBuilderWidget { This is to help with removing some boilerplate code. +### SelectorViewModelWidget + +Similar to `ViewModelBuilderWidget` but with the selector function. You can provide a selector, and if the the selector returns a new value, then the widget will be rebuilt. +You can wrap this with a `ViewModel.nonReactive` to supply the ViewModel from provider. + +```dart +class SelectorIntWidget + extends SelectorViewModelWidget { + const SelectorIntWidget({super.key}); + + @override + Widget build(BuildContext context, int value) { + return Text( + 'Likes:$value', + style: Theme.of(context).textTheme.headline4, + ); + } + + @override + int selector(SelectorViewModel model) => model.user.likes ?? 0; +} +``` + +### SelectorViewModelBuilder + +This will be run every time when the selector function returns a new value. + +```dart + SelectorViewModelBuilder( + builder: ( + BuildContext context, + String name, + Widget? child, + ) { + return Text( + name, + style: Theme.of(context).textTheme.headline4, + ); + }, + selector: (model) => model.user.name ?? '', + ), +``` + +Wrap this with a ViewModel.nonReactive to supply the ViewModel from provider. + ### Disable ViewModel Dispose An example of how to disable the dispose of a ViewModel. @@ -547,38 +592,23 @@ One thing that was common in a scenario with the first implementation of this ar ### Reactive Service Mixin -In the stacked library, we have a `ReactiveServiceMixin` which can be used to register values to "react" to. When any of these values change the listeners registered with this service will be notified to update their UI. This is definitely not the most efficient way but I have tested this with 1000 widgets with its ViewModel all updating on the screen and it works fine. If you follow general good code implementations and layout structuring you will have no problem keeping your app at 60fps no matter the size. - -There are three things you need to make a service reactive. - -1. Use the `ReactiveServiceMixin` with the service you want to make reactive -2. Wrap your values in an ReactiveValue. -3. Register your reactive values by calling `listenToReactiveValues`. A function provided by the mixin. - -Below is some source code for the non-theory coders out there like myself. +In the stacked library, we have a `ReactiveServiceMixin` which can be used to store a state that is needed across multiple ViewModels. The approach is simple. Just set the state variable and call `notifyListeners()`. This will notify any ViewModel that registered this `ReactiveServiceMixin`, and UI will be updated accordingly. ```dart -class InformationService with ReactiveServiceMixin { //1 - InformationService() { - //3 - listenToReactiveValues([_postCount]); - } - - //2 - ReactiveValue _postCount = ReactiveValue(initial: 0); - int get postCount => _postCount.value; - - void updatePostCount() { - _postCount.value++; - } +class PostsService with ReactiveServiceMixin { + int _postCount = 0; + int get postCount => _postCount; - void resetCount() { - _postCount.value = 0; + void setPostCount(int count) { + _postCount = count; + notifyListeners(); } } ``` -Easy peasy. This service can now be listened to when any of the properties passed into the `listenToReactiveValues` is changed. So how do you listen to these values? I'm glad you asked. Let's move onto the `ReactiveViewModel`. +Easy peasy. But how to register ReactiveService to multiple ViewModels? I'm glad you asked. Let's move onto the `ReactiveViewModel`. + +_Note_: In the past we were using a `ReactiveValues` to notify a state change, but we shifted to using `notifyListeners` since it has less boilerplate and is more consistent. ### ReactiveViewModel @@ -588,12 +618,12 @@ This ViewModel extends the `BaseViewModel` and adds a function that allows you t 2. Implement `reactiveServices` getter that returns a list of reactive services. ```dart -class WidgetOneViewModel extends ReactiveViewModel { - // You can use get_it service locator or pass it in through the constructor - final InformationService _informationService = locator(); +class AnyViewModel extends ReactiveViewModel { + final _postsService = locator(); + int get postCount => _postsService.postCount; - @override - List get reactiveServices => [_informationService]; + @override + List get reactiveServices => [_postsService]; } ``` @@ -1260,7 +1290,7 @@ logger: StackedLogger( Now the function to get your logger will be called `getStackedLogger`. If you want a more detailed guide on how to effectively log in your application read [this guide](https://www.filledstacks.com/post/flutter-logging-a-guide-to-use-it-effectively/) that we use for our production apps. -## Forms +## Forms ### Form Generation @@ -1303,28 +1333,29 @@ This will listen to the changes to the form and update the form value map. To ge Now that your FormView is setup, we can add validation. Validation offers both a security layer to avoid wrong data in forms and a rapid feedback for user to fix the input. -Stacked gives you two ways (that can be combined) to achieve that: global form validation or per-field validation. +Stacked gives you two ways (that can be combined) to achieve that: global form validation or per-field validation. #### Global form validation -By extending FormViewModel, you have access to the following methods that will help you setup the global form validation: +By extending FormViewModel, you have access to the following methods that will help you setup the global form validation: + - `setFormValidationMessage` (`setValidationMessage` prior v2.3.0): to be called in the `setFormStatus` to set a global validation message in case of error ; - `showFormValidationMessage` (`showValidation` prior v2.3.0): to be called from the View to know if any validation message should be displayed ; -- `formValidationMessage` (`validationMessage` prior v2.3.0): to be called from the view to display the actual validation message for the entire form if any. +- `formValidationMessage` (`validationMessage` prior v2.3.0): to be called from the view to display the actual validation message for the entire form if any. ```dart class ExampleFormViewModel extends FormViewModel { @override void setFormStatus() { - + // Set a validation message for the entire form if () { setFormValidationMessage('Error in the form, please check again'); } } -(...) +(...) class ExampleFormView extends StatelessWidget with $ExampleFormView { ExampleFormView({Key? key}) : super(key: key); @@ -1333,7 +1364,7 @@ class ExampleFormView extends StatelessWidget with $ExampleFormView { Widget build(BuildContext context) { return ViewModelBuilder.reactive( builder: (context, viewModel, child) => Scaffold( - (...) + (...) if (viewModel.showFormValidationMessage) Text( viewModel.formValidationMessage!, @@ -1344,9 +1375,10 @@ class ExampleFormView extends StatelessWidget with $ExampleFormView { #### Per-field validation -To achieve per-field validation, you can follow the same simple logic. By using the `@FormView` annotation to generate you form, Stacked also generates the following methods for each form field to help you: +To achieve per-field validation, you can follow the same simple logic. By using the `@FormView` annotation to generate you form, Stacked also generates the following methods for each form field to help you: + - `set[FieldName]ValidationMessage`: to be called in the `setFormStatus` to set a validation message for this field only ; -- `has[FieldName]ValidationMessage`: to be called from the View to know if any validation message should be displayed regarding this field ; +- `has[FieldName]ValidationMessage`: to be called from the View to know if any validation message should be displayed regarding this field ; - `[fieldName]ValidationMessage`: to be called from the view to display the actual validation message for this field. ```dart @@ -1378,7 +1410,7 @@ class ExampleFormViewModel extends FormViewModel { Widget build(BuildContext context) { return ViewModelBuilder.reactive( builder: (context, viewModel, child) => Scaffold( - (...) + (...) TextFormField( controller: passwordController, focusNode: passwordFocusNode, @@ -1391,9 +1423,10 @@ class ExampleFormViewModel extends FormViewModel { (...) ``` -**Hint**: in this example `passwordValidator` has been defined to return +**Hint**: in this example `passwordValidator` has been defined to return + - a `String` describing the validation message to be shown if any ; -- `null` if everything is fine, then no error will be shown. +- `null` if everything is fine, then no error will be shown. ```dart String? passwordValidator({String? value, int minimumLength = 6}) { @@ -1403,10 +1436,10 @@ String? passwordValidator({String? value, int minimumLength = 6}) { return null; } ``` -But feel free to implement your own logic and call `set[FieldName]ValidationMessage` when you need it. -A complete example can be found in [./example/lib/ui/form/example_form_view.dart](./example/lib/ui/form/example_form_view.dart). +But feel free to implement your own logic and call `set[FieldName]ValidationMessage` when you need it. +A complete example can be found in [./example/lib/ui/form/example_form_view.dart](./example/lib/ui/form/example_form_view.dart). ## Migrating from provider_architecture to Stacked diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 000000000..95ba151bb --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,34 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + # TODO: review these settings. + rules: + avoid_shadowing_type_parameters: false + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options + +analyzer: + exclude: + - example/ diff --git a/assets/banner.jpeg b/assets/banner.jpeg new file mode 100644 index 000000000..50767066b Binary files /dev/null and b/assets/banner.jpeg differ diff --git a/coverage/lcov.info b/coverage/lcov.info new file mode 100644 index 000000000..52a808928 --- /dev/null +++ b/coverage/lcov.info @@ -0,0 +1,3978 @@ +SF:lib\src\view_models\helpers\form_state_helper.dart +DA:7,2 +DA:10,2 +DA:16,1 +DA:17,1 +DA:18,3 +DA:21,1 +DA:23,1 +DA:26,1 +DA:29,2 +DA:30,2 +DA:34,1 +DA:37,1 +DA:40,1 +DA:41,1 +DA:42,3 +DA:43,1 +DA:44,1 +DA:49,0 +LF:18 +LH:17 +end_of_record +SF:lib\src\view_models\helpers\data_state_helper.dart +DA:7,4 +DA:9,2 +DA:10,2 +DA:14,8 +LF:4 +LH:4 +end_of_record +SF:lib\src\view_models\base_view_models.dart +DA:15,6 +DA:17,6 +DA:18,6 +DA:23,0 +DA:24,0 +DA:27,3 +DA:29,3 +DA:30,3 +DA:37,0 +DA:42,3 +DA:43,3 +DA:45,3 +DA:46,8 +DA:49,6 +DA:51,0 +DA:55,1 +DA:56,1 +DA:57,2 +DA:58,2 +DA:63,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:70,2 +DA:72,3 +DA:73,2 +DA:76,2 +DA:77,0 +DA:79,2 +DA:82,1 +DA:83,1 +DA:90,1 +DA:91,1 +DA:94,2 +DA:95,2 +DA:127,1 +DA:137,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:144,0 +DA:145,0 +DA:147,0 +DA:150,0 +DA:151,0 +DA:153,0 +DA:154,0 +DA:155,0 +DA:156,0 +DA:157,0 +DA:161,0 +DA:164,0 +DA:166,0 +DA:167,0 +DA:169,0 +LF:56 +LH:27 +end_of_record +SF:lib\src\view_models\helpers\builders_helpers.dart +DA:6,0 +DA:9,0 +DA:13,0 +DA:14,0 +DA:18,0 +DA:19,0 +LF:6 +LH:0 +end_of_record +SF:lib\src\view_models\helpers\busy_error_state_helper.dart +DA:7,12 +DA:10,6 +DA:13,5 +DA:17,2 +DA:18,6 +DA:19,2 +DA:23,2 +DA:24,2 +DA:28,1 +DA:33,1 +DA:35,1 +DA:45,2 +DA:47,2 +DA:49,2 +DA:54,0 +DA:56,2 +DA:60,2 +DA:62,2 +DA:64,2 +DA:69,12 +DA:72,6 +DA:75,2 +DA:78,1 +DA:79,2 +DA:83,4 +DA:86,2 +DA:87,2 +DA:90,2 +DA:92,2 +DA:94,2 +DA:100,3 +DA:101,9 +DA:102,3 +DA:105,2 +DA:108,2 +DA:111,2 +DA:112,2 +DA:114,1 +DA:119,1 +LF:39 +LH:38 +end_of_record +SF:lib\src\view_models\helpers\message_state_helper.dart +DA:7,0 +DA:10,0 +DA:13,0 +DA:16,0 +DA:18,0 +DA:19,0 +DA:23,2 +DA:24,2 +DA:29,2 +DA:30,6 +DA:31,2 +LF:11 +LH:5 +end_of_record +SF:lib\src\code_generation\router_annotation\extended_navigator.dart +DA:14,0 +DA:15,0 +DA:19,0 +DA:20,0 +DA:22,0 +DA:23,0 +DA:27,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:49,0 +DA:54,0 +DA:63,0 +DA:73,0 +DA:74,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:82,0 +DA:87,0 +DA:88,0 +DA:90,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:96,0 +DA:98,0 +DA:102,0 +DA:103,0 +DA:104,0 +DA:107,0 +DA:108,0 +DA:109,0 +DA:110,0 +DA:113,0 +DA:116,0 +DA:118,0 +DA:125,0 +DA:128,0 +DA:129,0 +DA:132,0 +DA:133,0 +DA:135,0 +DA:141,0 +DA:142,0 +DA:143,0 +DA:145,0 +DA:151,0 +DA:161,0 +DA:168,0 +DA:170,0 +DA:174,0 +DA:175,0 +DA:179,0 +DA:181,0 +DA:182,0 +DA:183,0 +DA:184,0 +DA:185,0 +DA:186,0 +DA:187,0 +DA:192,0 +DA:193,0 +DA:194,0 +DA:196,0 +DA:197,0 +DA:198,0 +DA:200,0 +DA:206,0 +DA:208,0 +DA:209,0 +DA:210,0 +DA:213,0 +DA:215,0 +DA:216,0 +DA:217,0 +DA:221,0 +DA:223,0 +DA:224,0 +DA:225,0 +DA:227,0 +DA:228,0 +DA:229,0 +DA:230,0 +DA:231,0 +DA:232,0 +DA:236,0 +DA:237,0 +DA:238,0 +DA:239,0 +DA:241,0 +DA:242,0 +DA:243,0 +DA:244,0 +DA:245,0 +DA:247,0 +DA:248,0 +DA:251,0 +DA:252,0 +DA:254,0 +DA:257,0 +DA:259,0 +DA:268,0 +DA:270,0 +DA:272,0 +DA:273,0 +DA:274,0 +DA:277,0 +DA:278,0 +DA:280,0 +DA:282,0 +DA:283,0 +DA:288,0 +DA:294,0 +DA:295,0 +DA:298,0 +DA:299,0 +DA:300,0 +DA:302,0 +DA:304,0 +DA:305,0 +DA:308,0 +DA:315,0 +DA:316,0 +DA:321,0 +DA:328,0 +DA:329,0 +DA:335,0 +DA:342,0 +DA:344,0 +DA:350,0 +DA:357,0 +DA:358,0 +DA:365,0 +DA:366,0 +DA:369,0 +DA:370,0 +DA:373,0 +DA:374,0 +DA:377,0 +DA:379,0 +DA:382,0 +DA:383,0 +DA:386,0 +DA:387,0 +DA:390,0 +DA:392,0 +DA:393,0 +DA:395,0 +DA:398,0 +DA:400,0 +DA:401,0 +DA:403,0 +DA:405,0 +DA:406,0 +DA:408,0 +DA:411,0 +DA:412,0 +DA:413,0 +DA:415,0 +DA:417,0 +DA:426,0 +DA:429,0 +DA:431,0 +DA:432,0 +DA:435,0 +DA:436,0 +DA:437,0 +DA:442,0 +DA:443,0 +DA:446,0 +DA:447,0 +LF:172 +LH:0 +end_of_record +SF:lib\src\code_generation\router_annotation\route_data.dart +DA:9,0 +DA:10,0 +DA:11,0 +DA:12,0 +DA:13,0 +DA:14,0 +DA:16,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:30,0 +DA:32,0 +DA:34,0 +DA:36,0 +DA:38,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:48,0 +DA:51,0 +DA:53,0 +DA:55,0 +DA:59,0 +DA:61,0 +DA:62,0 +DA:65,0 +DA:66,0 +DA:67,0 +DA:68,0 +DA:74,0 +DA:75,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:79,0 +DA:89,0 +DA:93,0 +DA:95,0 +DA:96,0 +DA:97,0 +DA:98,0 +DA:99,0 +LF:46 +LH:0 +end_of_record +SF:lib\src\code_generation\router_annotation\router_base.dart +DA:15,0 +DA:17,0 +DA:18,0 +DA:21,0 +DA:26,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:33,0 +DA:35,0 +DA:40,0 +DA:42,0 +DA:43,0 +DA:44,0 +DA:46,0 +DA:54,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:59,0 +DA:62,0 +DA:63,0 +DA:72,0 +DA:73,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:79,0 +DA:81,0 +DA:82,0 +LF:30 +LH:0 +end_of_record +SF:lib\src\code_generation\router_annotation\router_utils.dart +DA:8,0 +DA:9,0 +DA:10,0 +DA:14,0 +DA:16,0 +DA:17,0 +DA:20,0 +DA:21,0 +DA:26,0 +DA:27,0 +DA:30,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:45,0 +DA:47,0 +DA:54,0 +DA:55,0 +DA:59,0 +DA:62,0 +DA:71,0 +DA:80,0 +DA:81,0 +DA:86,0 +DA:87,0 +DA:95,0 +LF:27 +LH:0 +end_of_record +SF:lib\src\code_generation\router_annotation\uri_extension.dart +DA:2,0 +DA:3,0 +DA:5,0 +LF:3 +LH:0 +end_of_record +SF:lib\src\code_generation\router_annotation\parameters.dart +DA:7,7 +DA:9,0 +DA:11,0 +DA:13,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:22,0 +DA:23,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:31,0 +DA:32,0 +DA:34,0 +DA:35,0 +DA:38,0 +DA:39,0 +DA:41,0 +DA:42,0 +DA:44,0 +DA:45,0 +DA:50,0 +DA:51,0 +DA:54,0 +DA:57,0 +DA:61,0 +DA:62,0 +DA:64,0 +DA:65,0 +DA:70,0 +DA:71,0 +DA:74,0 +DA:77,0 +DA:81,0 +DA:82,0 +DA:84,0 +DA:85,0 +DA:90,0 +DA:91,0 +DA:94,0 +DA:97,0 +DA:101,0 +DA:102,0 +DA:104,0 +DA:105,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:114,0 +DA:121,0 +DA:122,0 +DA:124,0 +DA:125,0 +LF:54 +LH:1 +end_of_record +SF:lib\src\code_generation\router_annotation\route_matcher.dart +DA:11,0 +DA:12,0 +DA:14,0 +DA:16,0 +DA:17,0 +DA:18,0 +DA:22,0 +DA:23,0 +DA:26,0 +DA:27,0 +DA:30,0 +DA:31,0 +DA:32,0 +DA:42,0 +DA:44,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:58,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:67,0 +DA:68,0 +DA:83,0 +DA:91,0 +DA:93,0 +DA:95,0 +DA:97,0 +DA:99,0 +DA:101,0 +DA:103,0 +DA:105,0 +DA:109,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:114,0 +DA:115,0 +DA:116,0 +LF:42 +LH:0 +end_of_record +SF:lib\src\code_generation\router_annotation\route_def.dart +DA:9,0 +DA:13,0 +DA:15,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:25,0 +LF:8 +LH:0 +end_of_record +SF:lib\src\code_generation\router_annotation\transitions_builders.dart +DA:8,0 +DA:10,0 +DA:11,0 +DA:14,0 +DA:21,0 +DA:23,0 +DA:24,0 +DA:27,0 +DA:34,0 +DA:39,0 +DA:40,0 +DA:43,0 +DA:44,0 +DA:50,0 +DA:55,0 +DA:56,0 +DA:59,0 +DA:60,0 +DA:66,0 +DA:68,0 +DA:69,0 +DA:72,0 +DA:79,0 +DA:81,0 +DA:82,0 +DA:85,0 +DA:92,0 +DA:94,0 +DA:99,0 +DA:101,0 +DA:106,0 +DA:113,0 +DA:115,0 +DA:119,0 +DA:123,0 +DA:124,0 +DA:125,0 +DA:126,0 +LF:38 +LH:0 +end_of_record +SF:lib\src\mixins\listenable_service_mixin.dart +DA:8,0 +DA:11,0 +DA:12,0 +DA:13,0 +DA:14,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:18,0 +DA:24,4 +DA:25,8 +DA:29,1 +DA:30,2 +DA:34,4 +DA:37,8 +DA:38,4 +LF:16 +LH:7 +end_of_record +SF:lib\src\mixins\reactive_service_mixin.dart +DA:10,0 +DA:11,0 +DA:12,0 +DA:13,0 +DA:14,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:23,0 +DA:24,0 +DA:28,0 +DA:29,0 +DA:33,0 +DA:36,0 +DA:37,0 +LF:15 +LH:0 +end_of_record +SF:lib\src\reactive\reactive_list\reactive_list.dart +DA:13,0 +DA:14,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:27,0 +DA:28,0 +DA:30,0 +DA:34,0 +DA:37,0 +DA:38,0 +DA:39,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:48,0 +DA:50,0 +DA:51,0 +DA:54,0 +DA:56,0 +DA:58,0 +DA:59,0 +DA:62,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:71,0 +DA:72,0 +DA:75,0 +DA:77,0 +DA:78,0 +DA:81,0 +DA:83,0 +DA:84,0 +DA:86,0 +DA:91,0 +DA:93,0 +DA:94,0 +DA:98,0 +DA:99,0 +DA:100,0 +DA:104,0 +DA:105,0 +DA:106,0 +DA:110,0 +DA:112,0 +DA:113,0 +DA:114,0 +DA:115,0 +DA:127,0 +DA:128,0 +DA:129,0 +DA:131,0 +DA:132,0 +DA:133,0 +DA:134,0 +DA:135,0 +DA:136,0 +DA:137,0 +DA:156,0 +DA:157,0 +DA:159,0 +DA:161,0 +DA:163,0 +DA:165,0 +DA:167,0 +DA:169,0 +DA:171,0 +DA:175,0 +LF:72 +LH:0 +end_of_record +SF:lib\src\reactive\reactive_value\proxy_value.dart +DA:12,0 +DA:14,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:26,0 +DA:27,0 +DA:30,0 +DA:33,0 +DA:34,0 +DA:38,0 +DA:40,0 +DA:42,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:48,0 +DA:49,0 +DA:51,0 +DA:53,0 +DA:54,0 +DA:57,0 +DA:58,0 +DA:60,0 +DA:62,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:67,0 +DA:71,0 +DA:73,0 +DA:75,0 +DA:76,0 +LF:36 +LH:0 +end_of_record +SF:lib\src\reactive\reactive_value\reactive_value.dart +DA:9,0 +DA:10,0 +DA:11,0 +DA:57,0 +DA:62,0 +DA:64,0 +DA:65,0 +LF:7 +LH:0 +end_of_record +SF:lib\src\reactive\reactive_value\stored_value.dart +DA:8,0 +DA:9,0 +DA:10,0 +DA:12,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:26,0 +DA:28,0 +DA:30,0 +DA:31,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:38,0 +DA:40,0 +DA:42,0 +DA:43,0 +DA:44,0 +DA:45,0 +DA:48,0 +DA:49,0 +DA:51,0 +DA:53,0 +DA:54,0 +DA:57,0 +DA:58,0 +DA:60,0 +DA:62,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:67,0 +DA:71,0 +DA:73,0 +DA:75,0 +DA:76,0 +LF:37 +LH:0 +end_of_record +SF:lib\src\router\auto_router_x.dart +DA:12,0 +DA:14,0 +DA:16,0 +DA:19,0 +DA:21,0 +DA:24,0 +DA:26,0 +DA:28,0 +DA:30,0 +DA:32,0 +DA:37,0 +DA:39,0 +DA:42,0 +DA:48,0 +DA:50,0 +DA:53,0 +DA:56,0 +DA:58,0 +DA:59,0 +DA:61,0 +LF:20 +LH:0 +end_of_record +SF:lib\src\router\route\page_route_info.dart +DA:22,0 +DA:34,0 +DA:36,0 +DA:37,0 +DA:38,0 +DA:40,0 +DA:43,0 +DA:44,0 +DA:46,0 +DA:48,0 +DA:50,0 +DA:52,0 +DA:54,0 +DA:55,0 +DA:58,0 +DA:59,0 +DA:60,0 +DA:65,0 +DA:66,0 +DA:69,0 +DA:79,0 +DA:80,0 +DA:81,0 +DA:82,0 +DA:83,0 +DA:84,0 +DA:85,0 +DA:89,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:95,0 +DA:99,0 +DA:101,0 +DA:104,0 +DA:105,0 +DA:106,0 +DA:107,0 +DA:108,0 +DA:109,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:114,0 +DA:115,0 +DA:116,0 +DA:118,0 +DA:122,0 +DA:123,0 +DA:126,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:132,0 +DA:133,0 +DA:134,0 +DA:135,0 +DA:137,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:142,0 +DA:143,0 +DA:144,0 +LF:67 +LH:0 +end_of_record +SF:lib\src\router\widgets\stacked_tabs_router.dart +DA:34,0 +DA:41,0 +DA:95,0 +DA:96,0 +DA:97,0 +DA:99,0 +DA:105,0 +DA:106,0 +DA:114,0 +DA:118,0 +DA:120,0 +DA:121,0 +DA:122,0 +DA:123,0 +DA:124,0 +DA:125,0 +DA:126,0 +DA:129,0 +DA:130,0 +DA:132,0 +DA:133,0 +DA:134,0 +DA:135,0 +DA:136,0 +DA:137,0 +DA:139,0 +DA:140,0 +DA:142,0 +DA:144,0 +DA:145,0 +DA:151,0 +DA:153,0 +DA:154,0 +DA:155,0 +DA:156,0 +DA:158,0 +DA:169,0 +DA:180,0 +DA:188,0 +DA:190,0 +DA:200,0 +DA:201,0 +DA:203,0 +DA:205,0 +DA:206,0 +DA:208,0 +DA:210,0 +DA:211,0 +DA:212,0 +DA:213,0 +DA:216,0 +DA:219,0 +DA:221,0 +DA:222,0 +DA:225,0 +DA:227,0 +DA:228,0 +DA:229,0 +DA:230,0 +DA:231,0 +DA:232,0 +DA:233,0 +DA:234,0 +DA:236,0 +DA:237,0 +DA:238,0 +DA:243,0 +DA:245,0 +DA:246,0 +DA:247,0 +DA:248,0 +DA:249,0 +DA:250,0 +DA:255,0 +DA:257,0 +DA:258,0 +DA:259,0 +DA:260,0 +DA:262,0 +DA:263,0 +DA:264,0 +DA:265,0 +DA:266,0 +DA:267,0 +DA:268,0 +DA:269,0 +DA:270,0 +DA:271,0 +DA:276,0 +DA:277,0 +DA:278,0 +DA:280,0 +DA:281,0 +DA:282,0 +DA:284,0 +DA:285,0 +DA:286,0 +DA:289,0 +DA:297,0 +DA:298,0 +DA:303,0 +DA:312,0 +DA:322,0 +DA:323,0 +DA:331,0 +DA:333,0 +DA:335,0 +DA:336,0 +DA:338,0 +DA:340,0 +DA:341,0 +DA:344,0 +DA:345,0 +DA:346,0 +DA:347,0 +DA:348,0 +DA:350,0 +DA:355,0 +DA:357,0 +DA:358,0 +DA:359,0 +DA:360,0 +DA:363,0 +DA:364,0 +DA:365,0 +DA:366,0 +DA:367,0 +DA:368,0 +DA:372,0 +DA:374,0 +DA:375,0 +DA:376,0 +DA:378,0 +DA:379,0 +DA:380,0 +DA:381,0 +DA:382,0 +DA:383,0 +DA:385,0 +DA:387,0 +DA:388,0 +DA:404,0 +DA:419,0 +DA:427,0 +DA:428,0 +DA:435,0 +DA:437,0 +DA:438,0 +DA:439,0 +DA:440,0 +DA:441,0 +DA:444,0 +DA:448,0 +DA:449,0 +DA:450,0 +DA:455,0 +DA:456,0 +DA:457,0 +DA:461,0 +DA:463,0 +DA:464,0 +DA:465,0 +DA:466,0 +DA:467,0 +DA:471,0 +DA:473,0 +DA:475,0 +DA:476,0 +DA:477,0 +DA:478,0 +DA:479,0 +DA:480,0 +DA:482,0 +DA:483,0 +DA:484,0 +DA:486,0 +DA:487,0 +DA:489,0 +DA:490,0 +DA:491,0 +DA:492,0 +DA:493,0 +DA:494,0 +DA:496,0 +DA:503,0 +DA:507,0 +DA:508,0 +DA:510,0 +DA:512,0 +DA:522,0 +DA:535,0 +DA:543,0 +DA:544,0 +DA:551,0 +DA:553,0 +DA:554,0 +DA:555,0 +DA:556,0 +DA:557,0 +DA:558,0 +DA:559,0 +DA:560,0 +DA:561,0 +DA:563,0 +DA:564,0 +DA:565,0 +DA:566,0 +DA:569,0 +DA:570,0 +DA:575,0 +DA:576,0 +DA:577,0 +DA:578,0 +DA:581,0 +DA:582,0 +DA:586,0 +DA:588,0 +DA:589,0 +DA:590,0 +DA:591,0 +DA:592,0 +DA:596,0 +DA:598,0 +DA:600,0 +DA:601,0 +DA:602,0 +DA:603,0 +DA:604,0 +DA:605,0 +DA:607,0 +DA:608,0 +DA:609,0 +DA:611,0 +DA:612,0 +DA:614,0 +DA:615,0 +DA:616,0 +DA:617,0 +DA:618,0 +DA:619,0 +DA:621,0 +DA:628,0 +DA:632,0 +DA:633,0 +DA:635,0 +DA:637,0 +DA:645,0 +DA:655,0 +DA:663,0 +DA:664,0 +DA:669,0 +DA:671,0 +DA:672,0 +DA:673,0 +DA:674,0 +DA:675,0 +DA:676,0 +DA:677,0 +DA:678,0 +DA:679,0 +DA:681,0 +DA:682,0 +DA:687,0 +DA:689,0 +DA:690,0 +DA:691,0 +DA:692,0 +DA:693,0 +DA:697,0 +DA:699,0 +DA:701,0 +DA:702,0 +DA:703,0 +DA:704,0 +DA:705,0 +DA:706,0 +DA:707,0 +DA:709,0 +DA:710,0 +DA:711,0 +DA:713,0 +DA:714,0 +DA:716,0 +DA:717,0 +DA:718,0 +DA:719,0 +DA:720,0 +DA:723,0 +DA:730,0 +DA:731,0 +DA:733,0 +DA:735,0 +DA:743,0 +DA:744,0 +DA:746,0 +DA:748,0 +DA:750,0 +DA:751,0 +DA:757,0 +DA:758,0 +DA:759,0 +DA:760,0 +DA:761,0 +DA:768,0 +DA:771,0 +DA:774,0 +DA:775,0 +DA:780,0 +DA:782,0 +DA:783,0 +DA:786,0 +DA:787,0 +LF:312 +LH:0 +end_of_record +SF:lib\src\router\controller\controller_scope.dart +DA:11,0 +DA:18,0 +DA:20,0 +DA:23,0 +DA:25,0 +DA:27,0 +DA:29,0 +DA:35,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:48,0 +DA:50,0 +DA:58,0 +DA:63,0 +DA:65,0 +DA:67,0 +DA:69,0 +DA:72,0 +DA:74,0 +DA:82,0 +DA:87,0 +DA:89,0 +DA:91,0 +DA:93,0 +DA:96,0 +DA:98,0 +LF:28 +LH:0 +end_of_record +SF:lib\src\router\controller\routing_controller.dart +DA:40,0 +DA:43,0 +DA:45,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:54,0 +DA:55,0 +DA:58,0 +DA:59,0 +DA:60,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:72,0 +DA:74,0 +DA:76,0 +DA:78,0 +DA:79,0 +DA:80,0 +DA:81,0 +DA:83,0 +DA:84,0 +DA:88,0 +DA:89,0 +DA:91,0 +DA:92,0 +DA:95,0 +DA:96,0 +DA:99,0 +DA:100,0 +DA:103,0 +DA:104,0 +DA:108,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:114,0 +DA:121,0 +DA:125,0 +DA:130,0 +DA:133,0 +DA:134,0 +DA:136,0 +DA:141,0 +DA:145,0 +DA:146,0 +DA:147,0 +DA:149,0 +DA:159,0 +DA:160,0 +DA:163,0 +DA:166,0 +DA:167,0 +DA:168,0 +DA:170,0 +DA:171,0 +DA:176,0 +DA:180,0 +DA:181,0 +DA:182,0 +DA:186,0 +DA:191,0 +DA:193,0 +DA:194,0 +DA:195,0 +DA:196,0 +DA:197,0 +DA:201,0 +DA:203,0 +DA:208,0 +DA:210,0 +DA:212,0 +DA:214,0 +DA:218,0 +DA:223,0 +DA:229,0 +DA:230,0 +DA:233,0 +DA:236,0 +DA:237,0 +DA:239,0 +DA:240,0 +DA:241,0 +DA:245,0 +DA:246,0 +DA:247,0 +DA:259,0 +DA:269,0 +DA:271,0 +DA:272,0 +DA:275,0 +DA:277,0 +DA:279,0 +DA:281,0 +DA:283,0 +DA:291,0 +DA:293,0 +DA:301,0 +DA:303,0 +DA:305,0 +DA:307,0 +DA:308,0 +DA:311,0 +DA:312,0 +DA:321,0 +DA:323,0 +DA:334,0 +DA:335,0 +DA:336,0 +DA:338,0 +DA:339,0 +DA:340,0 +DA:341,0 +DA:342,0 +DA:343,0 +DA:344,0 +DA:351,0 +DA:354,0 +DA:355,0 +DA:356,0 +DA:357,0 +DA:360,0 +DA:363,0 +DA:364,0 +DA:365,0 +DA:366,0 +DA:369,0 +DA:370,0 +DA:376,0 +DA:377,0 +DA:378,0 +DA:398,0 +DA:405,0 +DA:409,0 +DA:410,0 +DA:412,0 +DA:414,0 +DA:415,0 +DA:416,0 +DA:420,0 +DA:422,0 +DA:425,0 +DA:427,0 +DA:428,0 +DA:434,0 +DA:436,0 +DA:438,0 +DA:439,0 +DA:440,0 +DA:441,0 +DA:442,0 +DA:444,0 +DA:449,0 +DA:450,0 +DA:452,0 +DA:453,0 +DA:456,0 +DA:458,0 +DA:459,0 +DA:461,0 +DA:468,0 +DA:471,0 +DA:472,0 +DA:473,0 +DA:474,0 +DA:475,0 +DA:477,0 +DA:481,0 +DA:482,0 +DA:483,0 +DA:484,0 +DA:485,0 +DA:486,0 +DA:488,0 +DA:489,0 +DA:490,0 +DA:491,0 +DA:495,0 +DA:496,0 +DA:498,0 +DA:501,0 +DA:502,0 +DA:503,0 +DA:504,0 +DA:509,0 +DA:511,0 +DA:512,0 +DA:515,0 +DA:517,0 +DA:519,0 +DA:520,0 +DA:521,0 +DA:523,0 +DA:524,0 +DA:525,0 +DA:527,0 +DA:530,0 +DA:533,0 +DA:534,0 +DA:536,0 +DA:537,0 +DA:540,0 +DA:541,0 +DA:542,0 +DA:543,0 +DA:544,0 +DA:548,0 +DA:549,0 +DA:551,0 +DA:552,0 +DA:553,0 +DA:554,0 +DA:557,0 +DA:559,0 +DA:560,0 +DA:564,0 +DA:565,0 +DA:566,0 +DA:571,0 +DA:574,0 +DA:575,0 +DA:578,0 +DA:579,0 +DA:580,0 +DA:587,0 +DA:590,0 +DA:592,0 +DA:597,0 +DA:605,0 +DA:607,0 +DA:613,0 +DA:614,0 +DA:622,0 +DA:628,0 +DA:629,0 +DA:630,0 +DA:631,0 +DA:634,0 +DA:635,0 +DA:636,0 +DA:640,0 +DA:643,0 +DA:655,1 +DA:660,1 +DA:667,0 +DA:668,0 +DA:671,0 +DA:675,0 +DA:676,0 +DA:677,0 +DA:682,0 +DA:683,0 +DA:684,0 +DA:687,0 +DA:689,0 +DA:690,0 +DA:691,0 +DA:692,0 +DA:693,0 +DA:696,0 +DA:699,0 +DA:700,0 +DA:705,0 +DA:716,0 +DA:719,0 +DA:721,0 +DA:722,0 +DA:723,0 +DA:729,0 +DA:735,0 +DA:739,0 +DA:740,0 +DA:742,0 +DA:750,0 +DA:751,0 +DA:759,0 +DA:760,0 +DA:762,0 +DA:764,0 +DA:765,0 +DA:772,0 +DA:778,0 +DA:779,0 +DA:780,0 +DA:781,0 +DA:792,0 +DA:793,0 +DA:794,0 +DA:795,0 +DA:798,0 +DA:800,0 +DA:801,0 +DA:802,0 +DA:803,0 +DA:805,0 +DA:813,0 +DA:814,0 +DA:816,0 +DA:818,0 +DA:824,0 +DA:825,0 +DA:826,0 +DA:827,0 +DA:828,0 +DA:832,0 +DA:833,0 +DA:841,0 +DA:844,0 +DA:845,0 +DA:846,0 +DA:848,0 +DA:849,0 +DA:855,0 +DA:857,0 +DA:859,0 +DA:863,0 +DA:865,0 +DA:866,0 +DA:869,0 +DA:870,0 +DA:871,0 +DA:872,0 +DA:875,0 +DA:876,0 +DA:877,0 +DA:878,0 +DA:881,0 +DA:882,0 +DA:884,0 +DA:888,0 +DA:890,0 +DA:893,0 +DA:895,0 +DA:896,0 +DA:902,0 +DA:903,0 +DA:905,0 +DA:908,0 +DA:911,0 +DA:912,0 +DA:913,0 +DA:914,0 +DA:915,0 +DA:916,0 +DA:917,0 +DA:921,0 +DA:923,0 +DA:924,0 +DA:925,0 +DA:929,0 +DA:930,0 +DA:931,0 +DA:932,0 +DA:933,0 +DA:934,0 +DA:939,0 +DA:943,0 +DA:951,0 +DA:958,0 +DA:961,0 +DA:965,0 +DA:966,0 +DA:967,0 +DA:968,0 +DA:971,0 +DA:976,0 +DA:981,0 +DA:982,0 +DA:983,0 +DA:984,0 +DA:987,0 +DA:991,0 +DA:992,0 +DA:999,0 +DA:1000,0 +DA:1001,0 +DA:1002,0 +DA:1003,0 +DA:1006,0 +DA:1010,0 +DA:1011,0 +DA:1012,0 +DA:1013,0 +DA:1016,0 +DA:1017,0 +DA:1018,0 +DA:1021,0 +DA:1027,0 +DA:1028,0 +DA:1029,0 +DA:1032,0 +DA:1034,0 +DA:1035,0 +DA:1038,0 +DA:1040,0 +DA:1041,0 +DA:1044,0 +DA:1049,0 +DA:1054,0 +DA:1056,0 +DA:1057,0 +DA:1059,0 +DA:1063,0 +DA:1068,0 +DA:1069,0 +DA:1070,0 +DA:1071,0 +DA:1072,0 +DA:1076,0 +DA:1077,0 +DA:1079,0 +DA:1080,0 +DA:1081,0 +DA:1084,0 +DA:1085,0 +DA:1086,0 +DA:1087,0 +DA:1093,0 +DA:1098,0 +DA:1100,0 +DA:1102,0 +DA:1105,0 +DA:1114,0 +DA:1118,0 +DA:1119,0 +DA:1120,0 +DA:1124,0 +DA:1126,0 +DA:1127,0 +DA:1129,0 +DA:1130,0 +DA:1131,0 +DA:1134,0 +DA:1143,0 +DA:1146,0 +DA:1150,0 +DA:1151,0 +DA:1152,0 +DA:1155,0 +DA:1157,0 +DA:1160,0 +DA:1165,0 +DA:1168,0 +DA:1169,0 +DA:1170,0 +DA:1171,0 +DA:1173,0 +DA:1174,0 +DA:1175,0 +DA:1182,0 +DA:1183,0 +DA:1185,0 +DA:1187,0 +DA:1188,0 +DA:1192,0 +DA:1197,0 +DA:1201,0 +DA:1204,0 +DA:1209,0 +DA:1210,0 +DA:1211,0 +DA:1213,0 +DA:1215,0 +DA:1216,0 +DA:1218,0 +DA:1219,0 +DA:1220,0 +DA:1222,0 +DA:1227,0 +DA:1228,0 +DA:1230,0 +DA:1233,0 +DA:1234,0 +DA:1235,0 +DA:1238,0 +DA:1244,0 +DA:1245,0 +DA:1246,0 +DA:1249,0 +DA:1255,0 +DA:1261,0 +DA:1262,0 +DA:1263,0 +DA:1264,0 +DA:1268,0 +DA:1271,0 +DA:1277,0 +DA:1283,0 +DA:1284,0 +DA:1288,0 +DA:1291,0 +DA:1292,0 +DA:1295,0 +DA:1296,0 +DA:1297,0 +DA:1298,0 +DA:1302,0 +DA:1319,0 +DA:1328,0 +DA:1330,0 +DA:1336,0 +DA:1339,0 +DA:1340,0 +DA:1342,0 +DA:1344,0 +DA:1345,0 +DA:1346,0 +DA:1350,0 +DA:1351,0 +DA:1352,0 +DA:1353,0 +DA:1355,0 +DA:1356,0 +DA:1357,0 +DA:1360,0 +DA:1365,0 +DA:1367,0 +DA:1369,0 +DA:1382,0 +DA:1388,0 +DA:1390,0 +DA:1391,0 +DA:1394,0 +DA:1397,0 +DA:1398,0 +DA:1399,0 +DA:1400,0 +DA:1406,0 +DA:1408,0 +LF:531 +LH:2 +end_of_record +SF:lib\src\router\controller\nested_router_delegate.dart +DA:16,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 +DA:35,0 +DA:36,0 +DA:40,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:62,0 +DA:64,0 +DA:65,0 +DA:67,0 +DA:72,0 +DA:73,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:79,0 +DA:80,0 +DA:81,0 +DA:82,0 +DA:84,0 +DA:88,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:95,0 +DA:96,0 +DA:97,0 +DA:100,0 +DA:101,0 +DA:104,0 +DA:105,0 +DA:106,0 +DA:108,0 +DA:109,0 +DA:111,0 +DA:114,0 +DA:115,0 +DA:116,0 +DA:117,0 +DA:118,0 +DA:119,0 +DA:120,0 +DA:123,0 +DA:124,0 +DA:127,0 +DA:129,0 +DA:130,0 +DA:131,0 +DA:134,0 +DA:138,0 +DA:145,0 +DA:156,0 +DA:157,0 +DA:161,0 +DA:163,0 +DA:165,0 +DA:166,0 +DA:169,0 +DA:171,0 +DA:172,0 +DA:175,0 +DA:176,0 +DA:177,0 +DA:181,0 +DA:183,0 +DA:184,0 +DA:185,0 +DA:186,0 +DA:187,0 +DA:189,0 +DA:191,0 +DA:192,0 +DA:193,0 +DA:194,0 +DA:195,0 +DA:196,0 +DA:208,0 +DA:217,0 +DA:223,0 +DA:226,0 +DA:228,0 +DA:229,0 +DA:230,0 +DA:231,0 +DA:232,0 +DA:233,0 +DA:235,0 +DA:238,0 +DA:240,0 +DA:243,0 +DA:244,0 +DA:245,0 +DA:247,0 +DA:248,0 +DA:251,0 +DA:254,0 +DA:256,0 +DA:257,0 +DA:258,0 +DA:259,0 +DA:261,0 +DA:262,0 +DA:263,0 +DA:265,0 +DA:266,0 +DA:267,0 +DA:268,0 +DA:269,0 +DA:270,0 +LF:118 +LH:0 +end_of_record +SF:lib\src\router\controller\stacked_route_guard.dart +DA:28,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:40,0 +DA:48,0 +DA:55,0 +DA:59,0 +DA:60,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:69,0 +DA:73,0 +DA:80,0 +DA:83,0 +DA:84,0 +DA:85,0 +DA:86,0 +DA:87,0 +DA:88,0 +DA:89,0 +DA:91,0 +DA:93,0 +DA:94,0 +DA:95,0 +DA:97,0 +DA:98,0 +DA:101,0 +DA:103,0 +DA:104,0 +DA:106,0 +DA:112,7 +DA:132,0 +DA:134,0 +DA:136,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:143,0 +DA:144,0 +DA:145,0 +DA:146,0 +DA:149,0 +DA:152,0 +DA:157,7 +DA:159,0 +DA:161,0 +DA:163,0 +DA:164,0 +DA:167,0 +DA:168,0 +DA:169,0 +DA:172,0 +DA:173,0 +DA:176,0 +DA:182,0 +DA:184,0 +DA:186,0 +DA:188,0 +DA:189,0 +DA:191,0 +DA:192,0 +DA:193,0 +DA:196,0 +DA:197,0 +DA:199,0 +DA:200,0 +DA:201,0 +DA:202,0 +DA:205,0 +DA:208,0 +DA:213,0 +DA:215,0 +DA:217,0 +DA:219,0 +DA:220,0 +DA:222,0 +DA:223,0 +DA:224,0 +DA:226,0 +DA:235,0 +DA:237,0 +DA:239,0 +DA:240,0 +LF:86 +LH:2 +end_of_record +SF:lib\src\router\route\route_data.dart +DA:8,0 +DA:10,1 +DA:20,0 +DA:22,3 +DA:24,0 +DA:25,0 +DA:28,0 +DA:29,0 +DA:31,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:38,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:54,0 +DA:55,0 +DA:58,0 +DA:60,3 +DA:62,0 +DA:64,0 +DA:66,0 +DA:68,0 +DA:70,0 +DA:72,0 +DA:73,0 +DA:74,0 +DA:77,0 +DA:78,0 +DA:79,0 +DA:84,0 +DA:86,0 +DA:88,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:98,0 +DA:99,0 +DA:100,0 +DA:102,0 +LF:44 +LH:3 +end_of_record +SF:lib\src\router\controller\root_stack_router.dart +DA:7,1 +DA:8,1 +DA:12,2 +DA:15,0 +DA:16,0 +DA:26,0 +DA:37,0 +DA:38,0 +DA:42,0 +DA:46,0 +DA:52,0 +DA:53,0 +DA:57,0 +DA:66,0 +DA:78,0 +DA:86,0 +DA:96,0 +DA:97,0 +DA:99,0 +DA:100,0 +DA:101,0 +DA:102,0 +DA:105,0 +DA:107,0 +DA:113,0 +DA:114,0 +LF:26 +LH:3 +end_of_record +SF:lib\src\router\matcher\route_match.dart +DA:25,8 +DA:42,0 +DA:44,0 +DA:46,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:55,0 +DA:56,0 +DA:59,0 +DA:74,0 +DA:75,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:79,0 +DA:80,0 +DA:81,0 +DA:82,0 +DA:83,0 +DA:84,0 +DA:85,0 +DA:86,0 +DA:87,0 +DA:91,0 +DA:94,0 +DA:95,0 +DA:96,0 +DA:97,0 +DA:98,0 +DA:99,0 +DA:100,0 +DA:101,0 +DA:102,0 +DA:103,0 +DA:104,0 +DA:105,0 +DA:106,0 +DA:107,0 +DA:109,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:114,0 +DA:115,0 +DA:116,0 +DA:117,0 +DA:118,0 +DA:119,0 +DA:120,0 +DA:121,0 +DA:122,0 +DA:124,0 +DA:126,0 +DA:129,0 +LF:57 +LH:1 +end_of_record +SF:lib\src\router\navigation_failure.dart +DA:8,0 +DA:14,0 +DA:16,0 +DA:18,0 +DA:26,0 +DA:28,0 +DA:30,0 +LF:7 +LH:0 +end_of_record +SF:lib\src\router\widgets\nested_router.dart +DA:15,0 +DA:24,0 +DA:26,0 +DA:38,0 +DA:49,0 +DA:50,0 +DA:52,0 +DA:53,0 +DA:54,0 +DA:56,0 +DA:62,0 +DA:63,0 +DA:66,0 +DA:67,0 +DA:74,0 +DA:79,0 +DA:81,0 +DA:82,0 +DA:83,0 +DA:84,0 +DA:85,0 +DA:86,0 +DA:87,0 +DA:90,0 +DA:91,0 +DA:93,0 +DA:94,0 +DA:95,0 +DA:96,0 +DA:97,0 +DA:99,0 +DA:100,0 +DA:101,0 +DA:103,0 +DA:106,0 +DA:107,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:117,0 +DA:119,0 +DA:120,0 +DA:121,0 +DA:122,0 +DA:123,0 +DA:124,0 +DA:126,0 +DA:127,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:132,0 +DA:133,0 +DA:135,0 +DA:137,0 +DA:138,0 +DA:144,0 +DA:146,0 +DA:147,0 +DA:148,0 +DA:149,0 +DA:150,0 +DA:151,0 +DA:169,0 +DA:180,0 +DA:182,0 +DA:184,0 +DA:191,0 +DA:196,0 +DA:198,0 +DA:199,0 +DA:200,0 +DA:201,0 +DA:202,0 +DA:203,0 +DA:204,0 +DA:205,0 +DA:208,0 +DA:209,0 +DA:211,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:215,0 +DA:218,0 +DA:219,0 +DA:220,0 +DA:221,0 +DA:223,0 +DA:224,0 +DA:228,0 +DA:230,0 +DA:231,0 +DA:232,0 +DA:233,0 +DA:234,0 +DA:238,0 +DA:240,0 +DA:241,0 +DA:243,0 +DA:244,0 +DA:245,0 +DA:246,0 +DA:248,0 +DA:249,0 +DA:250,0 +DA:251,0 +DA:252,0 +DA:253,0 +DA:254,0 +DA:255,0 +DA:256,0 +LF:112 +LH:0 +end_of_record +SF:lib\src\router\common\route_observer.dart +DA:9,0 +DA:10,0 +DA:16,0 +DA:19,0 +DA:22,0 +DA:26,0 +DA:29,0 +DA:31,0 +DA:38,0 +DA:40,0 +DA:43,0 +DA:44,0 +DA:47,0 +DA:51,0 +DA:53,0 +DA:54,0 +DA:57,0 +DA:60,0 +DA:63,0 +DA:66,0 +DA:69,0 +DA:72,0 +DA:85,0 +DA:87,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:92,0 +DA:102,0 +DA:103,0 +DA:104,0 +DA:105,0 +DA:109,0 +DA:112,0 +DA:114,0 +DA:115,0 +DA:120,0 +DA:123,0 +DA:125,0 +DA:126,0 +DA:131,0 +DA:133,0 +DA:134,0 +DA:135,0 +DA:137,0 +DA:140,0 +DA:141,0 +DA:144,0 +DA:146,0 +DA:149,0 +DA:150,0 +DA:156,0 +DA:158,0 +DA:159,0 +DA:160,0 +DA:161,0 +DA:164,0 +DA:165,0 +DA:176,0 +DA:181,0 +DA:182,0 +DA:183,0 +LF:62 +LH:0 +end_of_record +SF:lib\src\router\common\parameters.dart +DA:7,0 +DA:9,0 +DA:11,0 +DA:13,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:22,0 +DA:23,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:31,0 +DA:32,0 +DA:34,0 +DA:35,0 +DA:38,0 +DA:39,0 +DA:41,0 +DA:42,0 +DA:44,0 +DA:45,0 +DA:50,0 +DA:51,0 +DA:54,0 +DA:57,0 +DA:61,0 +DA:62,0 +DA:64,0 +DA:65,0 +DA:70,0 +DA:71,0 +DA:74,0 +DA:77,0 +DA:81,0 +DA:82,0 +DA:84,0 +DA:85,0 +DA:90,0 +DA:91,0 +DA:94,0 +DA:97,0 +DA:101,0 +DA:102,0 +DA:104,0 +DA:105,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:114,0 +DA:121,0 +DA:122,0 +DA:124,0 +DA:125,0 +LF:54 +LH:0 +end_of_record +SF:lib\src\router\common\transitions_builders.dart +DA:6,0 +DA:10,0 +DA:12,0 +DA:13,0 +DA:16,0 +DA:23,0 +DA:25,0 +DA:26,0 +DA:29,0 +DA:36,0 +DA:41,0 +DA:42,0 +DA:45,0 +DA:46,0 +DA:52,0 +DA:57,0 +DA:58,0 +DA:61,0 +DA:62,0 +DA:68,0 +DA:70,0 +DA:71,0 +DA:74,0 +DA:81,0 +DA:83,0 +DA:84,0 +DA:87,0 +DA:94,0 +DA:96,0 +DA:101,0 +DA:103,0 +DA:108,0 +LF:32 +LH:0 +end_of_record +SF:lib\src\router\stacked_page.dart +DA:22,0 +DA:24,0 +DA:26,0 +DA:33,0 +DA:34,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:44,0 +DA:46,0 +DA:47,0 +DA:50,0 +DA:52,0 +DA:53,0 +DA:54,0 +DA:55,0 +DA:61,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:71,0 +DA:77,0 +DA:85,0 +DA:87,0 +DA:93,0 +DA:95,0 +DA:97,0 +DA:101,0 +DA:102,0 +DA:104,0 +DA:105,0 +DA:107,0 +DA:108,0 +DA:110,0 +DA:111,0 +DA:116,0 +DA:118,0 +DA:120,0 +DA:121,0 +DA:123,0 +DA:124,0 +DA:126,0 +DA:127,0 +DA:129,0 +DA:130,0 +DA:135,0 +DA:137,0 +DA:139,0 +DA:140,0 +DA:142,0 +DA:143,0 +DA:145,0 +DA:146,0 +DA:148,0 +DA:149,0 +DA:151,0 +DA:157,0 +DA:162,0 +DA:165,0 +DA:168,0 +DA:171,0 +DA:172,0 +DA:174,0 +DA:177,0 +DA:178,0 +DA:179,0 +DA:180,0 +DA:181,0 +DA:182,0 +DA:183,0 +DA:184,0 +DA:187,0 +DA:193,0 +DA:196,0 +DA:203,0 +DA:208,0 +DA:209,0 +DA:210,0 +DA:213,0 +DA:214,0 +DA:215,0 +DA:218,0 +DA:219,0 +DA:221,0 +DA:223,0 +DA:225,0 +DA:226,0 +DA:228,0 +DA:229,0 +DA:231,0 +DA:234,0 +DA:235,0 +DA:236,0 +DA:237,0 +DA:238,0 +DA:239,0 +DA:242,0 +DA:248,0 +DA:251,0 +DA:255,0 +DA:263,0 +DA:267,0 +DA:268,0 +DA:275,0 +DA:282,0 +DA:292,0 +DA:298,0 +DA:306,0 +DA:308,0 +DA:314,0 +DA:316,0 +DA:318,0 +DA:320,0 +DA:321,0 +DA:323,0 +DA:324,0 +DA:326,0 +DA:327,0 +DA:329,0 +DA:330,0 +DA:332,0 +DA:333,0 +DA:337,0 +DA:344,0 +DA:353,0 +DA:356,0 +DA:359,0 +DA:375,0 +DA:389,0 +DA:398,0 +DA:400,0 +DA:401,0 +DA:402,0 +DA:404,0 +LF:135 +LH:0 +end_of_record +SF:lib\src\router\widgets\custom_cupertino_transitions_builder.dart +DA:37,0 +DA:43,0 +DA:49,0 +DA:100,0 +DA:102,0 +DA:105,0 +DA:108,0 +DA:111,0 +DA:112,0 +DA:114,0 +DA:115,0 +DA:117,0 +DA:119,0 +DA:122,0 +DA:126,0 +DA:129,0 +DA:132,0 +DA:135,0 +DA:136,0 +DA:147,0 +DA:148,0 +DA:159,0 +DA:170,0 +DA:172,0 +DA:175,0 +DA:178,0 +DA:181,0 +DA:184,0 +DA:186,0 +DA:188,0 +DA:192,0 +DA:196,0 +DA:202,0 +DA:205,0 +DA:206,0 +DA:218,0 +DA:220,0 +DA:222,0 +DA:223,0 +DA:224,0 +DA:242,0 +DA:254,0 +DA:255,0 +DA:256,0 +DA:263,0 +DA:267,0 +DA:268,0 +DA:269,0 +DA:276,0 +DA:279,0 +DA:313,0 +DA:319,0 +DA:323,0 +DA:329,0 +DA:330,0 +DA:338,0 +DA:339,0 +DA:355,0 +DA:363,0 +DA:374,0 +DA:377,0 +DA:382,0 +DA:385,0 +DA:389,0 +DA:390,0 +DA:402,0 +DA:404,0 +DA:405,0 +DA:406,0 +DA:407,0 +DA:410,0 +DA:411,0 +DA:413,0 +DA:414,0 +DA:415,0 +DA:435,0 +DA:441,0 +DA:446,0 +DA:447,0 +DA:450,0 +DA:455,0 +DA:456,0 +DA:466,0 +DA:468,0 +DA:469,0 +DA:470,0 +DA:471,0 +DA:474,0 +DA:475,0 +DA:476,0 +DA:494,0 +DA:499,0 +DA:507,0 +DA:509,0 +DA:518,0 +DA:520,0 +DA:521,0 +DA:522,0 +DA:523,0 +DA:524,0 +DA:525,0 +DA:528,0 +DA:530,0 +DA:531,0 +DA:534,0 +DA:535,0 +DA:536,0 +DA:537,0 +DA:540,0 +DA:541,0 +DA:542,0 +DA:543,0 +DA:544,0 +DA:547,0 +DA:548,0 +DA:549,0 +DA:550,0 +DA:551,0 +DA:552,0 +DA:555,0 +DA:556,0 +DA:559,0 +DA:560,0 +DA:563,0 +DA:564,0 +DA:567,0 +DA:568,0 +DA:569,0 +DA:570,0 +DA:571,0 +DA:576,0 +DA:578,0 +DA:581,0 +DA:582,0 +DA:583,0 +DA:584,0 +DA:585,0 +DA:587,0 +DA:588,0 +DA:589,0 +DA:594,0 +DA:595,0 +DA:620,0 +DA:624,0 +DA:632,0 +DA:633,0 +DA:638,0 +DA:651,0 +DA:652,0 +DA:654,0 +DA:661,0 +DA:662,0 +DA:663,0 +DA:664,0 +DA:667,0 +DA:668,0 +DA:672,0 +DA:675,0 +DA:677,0 +DA:678,0 +DA:679,0 +DA:680,0 +DA:681,0 +DA:686,0 +DA:691,0 +DA:692,0 +DA:693,0 +DA:695,0 +DA:697,0 +DA:706,7 +DA:708,0 +DA:711,0 +DA:753,0 +DA:760,0 +DA:762,0 +DA:763,0 +DA:764,0 +DA:767,0 +DA:769,0 +DA:770,0 +DA:771,0 +DA:773,0 +DA:776,0 +DA:777,0 +DA:778,0 +DA:779,0 +DA:780,0 +DA:781,0 +DA:782,0 +DA:787,0 +DA:789,0 +DA:790,0 +DA:792,0 +DA:795,0 +DA:797,0 +DA:798,0 +DA:800,0 +DA:803,0 +DA:805,0 +DA:808,0 +DA:810,0 +DA:811,0 +DA:814,0 +DA:815,0 +DA:817,0 +DA:819,0 +DA:820,0 +DA:826,0 +DA:829,0 +DA:833,0 +DA:835,0 +DA:836,0 +DA:864,0 +DA:865,0 +DA:866,0 +DA:868,0 +DA:869,0 +DA:873,0 +DA:874,0 +DA:877,0 +DA:878,0 +DA:884,0 +DA:885,0 +DA:886,0 +DA:888,0 +DA:889,0 +DA:890,0 +DA:891,0 +DA:892,0 +DA:893,0 +DA:900,0 +DA:902,0 +DA:910,0 +LF:233 +LH:1 +end_of_record +SF:lib\src\router\controller\navigation_history\native_navigation_history.dart +DA:10,1 +DA:17,0 +DA:19,0 +DA:20,0 +DA:21,0 +DA:24,0 +DA:25,0 +DA:27,0 +DA:28,0 +DA:30,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:36,0 +DA:37,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:45,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:53,0 +DA:55,0 +DA:64,0 +DA:66,0 +DA:69,0 +DA:70,0 +DA:71,0 +DA:73,0 +DA:74,0 +DA:76,0 +DA:78,0 +LF:33 +LH:1 +end_of_record +SF:lib\src\router\parser\route_information_parser.dart +DA:13,0 +DA:15,0 +DA:17,0 +DA:19,0 +DA:20,0 +DA:23,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:35,0 +DA:39,0 +DA:48,1 +DA:50,0 +DA:52,0 +DA:54,1 +DA:58,1 +DA:59,1 +DA:65,0 +DA:67,0 +DA:68,0 +DA:69,0 +DA:71,0 +DA:72,0 +DA:73,0 +DA:79,0 +DA:81,0 +DA:83,0 +DA:85,0 +DA:89,0 +DA:90,0 +DA:92,0 +DA:93,0 +DA:102,0 +DA:103,0 +DA:104,0 +DA:108,1 +DA:110,1 +DA:111,1 +DA:113,0 +DA:114,0 +DA:115,0 +DA:118,0 +DA:119,0 +DA:120,0 +DA:121,0 +DA:122,0 +DA:124,0 +DA:125,0 +DA:127,0 +DA:133,0 +DA:134,0 +DA:136,0 +DA:138,0 +DA:143,0 +DA:147,0 +DA:148,0 +DA:150,0 +DA:151,0 +DA:153,0 +DA:159,0 +DA:162,0 +DA:163,0 +DA:164,0 +DA:166,0 +DA:167,0 +DA:169,0 +DA:174,0 +DA:175,0 +DA:176,0 +DA:177,0 +LF:70 +LH:7 +end_of_record +SF:lib\src\router\controller\navigation_history\navigation_history_base.dart +DA:10,1 +DA:11,1 +DA:14,0 +DA:15,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:20,0 +DA:25,0 +DA:27,0 +DA:29,0 +DA:33,0 +DA:34,0 +DA:35,0 +DA:37,0 +DA:42,0 +DA:43,0 +DA:44,0 +DA:48,0 +DA:49,0 +DA:50,0 +DA:54,0 +DA:55,0 +DA:58,0 +LF:24 +LH:2 +end_of_record +SF:lib\src\router\controller\pageless_routes_observer.dart +DA:7,0 +DA:9,0 +DA:10,0 +DA:11,0 +DA:12,0 +DA:16,0 +DA:17,0 +DA:19,0 +DA:23,0 +DA:25,0 +DA:28,0 +DA:30,0 +DA:33,0 +DA:35,0 +DA:38,0 +DA:40,0 +LF:16 +LH:0 +end_of_record +SF:lib\src\router\matcher\route_matcher.dart +DA:13,3 +DA:15,1 +DA:16,1 +DA:17,2 +DA:18,2 +DA:20,1 +DA:23,3 +DA:25,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:34,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:50,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:62,0 +DA:65,0 +DA:66,0 +DA:67,0 +DA:69,0 +DA:70,0 +DA:76,0 +DA:78,0 +DA:79,0 +DA:80,0 +DA:81,0 +DA:87,0 +DA:88,0 +DA:90,0 +DA:96,0 +DA:100,0 +DA:101,0 +DA:102,0 +DA:103,0 +DA:105,0 +DA:106,0 +DA:109,0 +DA:110,0 +DA:113,0 +DA:114,0 +DA:115,0 +DA:116,0 +DA:117,0 +DA:119,0 +DA:120,0 +DA:124,0 +DA:126,0 +DA:127,0 +DA:128,0 +DA:129,0 +DA:131,0 +DA:133,0 +DA:134,0 +DA:141,0 +DA:142,0 +DA:143,0 +DA:146,0 +DA:152,0 +DA:154,0 +DA:155,0 +DA:161,0 +DA:167,0 +DA:175,0 +DA:177,0 +DA:179,0 +DA:180,0 +DA:182,0 +DA:186,0 +DA:187,0 +DA:188,0 +DA:192,0 +DA:193,0 +DA:194,0 +DA:195,0 +DA:196,0 +DA:197,0 +DA:198,0 +DA:203,0 +DA:204,0 +DA:208,0 +DA:209,0 +DA:210,0 +DA:211,0 +DA:212,0 +DA:213,0 +DA:214,0 +DA:218,0 +DA:219,0 +DA:220,0 +DA:221,0 +DA:225,0 +DA:226,0 +DA:229,0 +DA:230,0 +DA:234,0 +DA:235,0 +DA:236,0 +DA:237,0 +DA:238,0 +DA:239,0 +DA:243,0 +DA:248,0 +DA:250,0 +DA:253,0 +DA:256,0 +DA:257,0 +DA:258,0 +DA:259,0 +DA:260,0 +DA:261,0 +DA:262,0 +DA:263,0 +DA:265,0 +DA:266,0 +DA:267,0 +DA:268,0 +DA:269,0 +DA:271,0 +DA:272,0 +DA:276,0 +DA:278,0 +DA:279,0 +DA:280,0 +DA:281,0 +DA:282,0 +DA:283,0 +DA:284,0 +DA:286,0 +LF:136 +LH:7 +end_of_record +SF:lib\src\router\provider\stacked_route_information_provider.dart +DA:12,0 +DA:18,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:30,0 +DA:34,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:42,0 +DA:43,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:52,0 +DA:53,0 +DA:56,0 +DA:57,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:67,0 +DA:70,0 +DA:72,0 +DA:73,0 +DA:76,0 +DA:78,0 +DA:79,0 +DA:82,0 +DA:88,0 +DA:89,0 +DA:92,0 +DA:95,0 +DA:96,0 +DA:100,0 +DA:102,0 +DA:103,0 +LF:40 +LH:0 +end_of_record +SF:lib\src\router\route\route_config.dart +DA:17,1 +DA:28,1 +DA:30,0 +DA:32,2 +DA:34,0 +DA:36,0 +DA:38,0 +LF:7 +LH:3 +end_of_record +SF:lib\src\router\route\route_data_scope.dart +DA:7,0 +DA:11,0 +DA:13,0 +DA:14,0 +DA:15,0 +DA:17,0 +DA:23,0 +DA:27,0 +DA:29,0 +LF:9 +LH:0 +end_of_record +SF:lib\src\router\transitions\stacked_page_route.dart +DA:4,0 +DA:9,0 +DA:14,0 +DA:17,0 +DA:20,0 +DA:23,0 +DA:26,0 +DA:29,0 +DA:30,0 +DA:37,0 +DA:38,0 +DA:47,0 +LF:12 +LH:0 +end_of_record +SF:lib\src\router\widgets\route_navigator.dart +DA:16,0 +DA:24,0 +DA:26,0 +DA:27,0 +DA:33,0 +DA:35,0 +DA:36,0 +DA:37,0 +DA:41,0 +DA:42,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:54,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:62,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:67,0 +DA:68,0 +DA:69,0 +DA:72,0 +DA:73,0 +DA:74,0 +DA:75,0 +DA:78,0 +DA:79,0 +DA:80,0 +DA:81,0 +DA:86,0 +DA:87,0 +DA:88,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:95,0 +LF:42 +LH:0 +end_of_record +SF:lib\src\utils.dart +DA:1,0 +DA:2,0 +DA:5,0 +DA:6,0 +LF:4 +LH:0 +end_of_record +SF:lib\src\router\widgets\wrapped_route.dart +DA:6,0 +DA:9,0 +DA:11,0 +LF:3 +LH:0 +end_of_record +SF:lib\src\router\widgets\auto_tabs_scaffold.dart +DA:56,0 +DA:91,0 +DA:93,0 +DA:95,0 +DA:96,0 +DA:97,0 +DA:98,0 +DA:99,0 +DA:100,0 +DA:101,0 +DA:102,0 +DA:103,0 +DA:104,0 +DA:105,0 +DA:106,0 +DA:107,0 +DA:108,0 +DA:109,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:114,0 +DA:115,0 +DA:116,0 +DA:117,0 +DA:118,0 +DA:119,0 +DA:120,0 +DA:121,0 +DA:122,0 +DA:123,0 +DA:124,0 +DA:125,0 +DA:126,0 +DA:127,0 +DA:128,0 +DA:130,0 +DA:134,0 +DA:135,0 +DA:136,0 +DA:141,0 +DA:143,0 +LF:43 +LH:0 +end_of_record +SF:lib\src\router\widgets\deferred_widget.dart +DA:17,0 +DA:22,0 +DA:27,0 +DA:28,0 +DA:30,0 +DA:31,0 +DA:32,0 +DA:33,0 +DA:36,0 +DA:39,0 +DA:40,0 +DA:44,0 +DA:49,0 +DA:53,0 +DA:54,0 +DA:56,0 +DA:57,0 +DA:59,0 +DA:62,0 +DA:63,0 +DA:64,0 +DA:65,0 +DA:69,0 +DA:73,0 +DA:74,0 +DA:75,0 +DA:77,0 +DA:84,0 +DA:88,0 +LF:29 +LH:0 +end_of_record +SF:lib\src\router\widgets\stacked_leading_button.dart +DA:12,0 +DA:14,0 +DA:16,0 +DA:18,0 +DA:34,0 +DA:42,0 +DA:45,0 +DA:47,0 +DA:48,0 +DA:54,0 +DA:56,0 +DA:57,0 +DA:58,0 +DA:61,0 +DA:63,0 +DA:64,0 +DA:67,0 +DA:69,0 +DA:70,0 +DA:71,0 +DA:72,0 +DA:73,0 +DA:75,0 +DA:76,0 +DA:78,0 +DA:79,0 +DA:82,0 +DA:86,0 +DA:87,0 +DA:88,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:95,0 +DA:96,0 +DA:97,0 +DA:98,0 +DA:101,0 +DA:104,0 +DA:106,0 +DA:107,0 +DA:108,0 +DA:112,0 +DA:113,0 +DA:118,0 +DA:119,0 +DA:122,0 +DA:123,0 +LF:48 +LH:0 +end_of_record +SF:lib\src\router\widgets\stacked_page_view.dart +DA:8,0 +DA:15,0 +DA:35,0 +DA:36,0 +DA:45,0 +DA:47,0 +DA:48,0 +DA:49,0 +DA:52,0 +DA:54,0 +DA:55,0 +DA:56,0 +DA:60,0 +DA:62,0 +DA:63,0 +DA:66,0 +DA:67,0 +DA:68,0 +DA:69,0 +DA:73,0 +DA:74,0 +DA:75,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:79,0 +DA:84,0 +DA:85,0 +DA:89,0 +DA:90,0 +DA:91,0 +DA:93,0 +DA:94,0 +DA:95,0 +DA:96,0 +DA:98,0 +DA:99,0 +DA:101,0 +DA:102,0 +DA:103,0 +DA:104,0 +DA:106,0 +DA:107,0 +DA:108,0 +DA:109,0 +DA:110,0 +DA:111,0 +DA:113,0 +DA:115,0 +DA:117,0 +DA:118,0 +DA:119,0 +DA:124,0 +DA:125,0 +DA:126,0 +DA:127,0 +DA:128,0 +DA:129,0 +DA:131,0 +DA:135,0 +DA:137,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:142,0 +DA:143,0 +DA:144,0 +DA:145,0 +DA:146,0 +LF:70 +LH:0 +end_of_record +SF:lib\src\router\widgets\stacked_tab_view.dart +DA:17,0 +DA:24,0 +DA:44,0 +DA:45,0 +DA:49,0 +DA:55,0 +DA:60,0 +DA:62,0 +DA:64,0 +DA:65,0 +DA:66,0 +DA:67,0 +DA:68,0 +DA:71,0 +DA:73,0 +DA:74,0 +DA:75,0 +DA:76,0 +DA:80,0 +DA:82,0 +DA:83,0 +DA:85,0 +DA:86,0 +DA:89,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:95,0 +DA:100,0 +DA:101,0 +DA:105,0 +DA:106,0 +DA:107,0 +DA:111,0 +DA:112,0 +DA:114,0 +DA:115,0 +DA:118,0 +DA:120,0 +DA:121,0 +DA:122,0 +DA:125,0 +DA:127,0 +DA:128,0 +DA:129,0 +DA:131,0 +DA:132,0 +DA:135,0 +DA:136,0 +DA:137,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:142,0 +DA:143,0 +DA:144,0 +DA:146,0 +DA:148,0 +DA:150,0 +DA:151,0 +DA:152,0 +DA:157,0 +DA:158,0 +DA:160,0 +DA:162,0 +DA:163,0 +DA:164,0 +DA:165,0 +DA:166,0 +DA:167,0 +DA:169,0 +DA:170,0 +DA:171,0 +DA:172,0 +DA:173,0 +DA:174,0 +DA:175,0 +DA:176,0 +DA:177,0 +DA:180,0 +DA:185,0 +DA:187,0 +DA:188,0 +DA:189,0 +DA:190,0 +DA:191,0 +DA:195,0 +DA:196,0 +DA:197,0 +DA:198,0 +DA:199,0 +DA:200,0 +DA:201,0 +DA:202,0 +DA:203,0 +DA:204,0 +DA:205,0 +LF:100 +LH:0 +end_of_record +SF:lib\src\view_models\data_models\multiple_data_models.dart +DA:10,4 +DA:12,4 +DA:23,1 +DA:24,2 +DA:26,1 +DA:29,1 +DA:31,2 +DA:32,1 +DA:35,1 +DA:36,1 +DA:38,3 +DA:39,4 +DA:40,2 +DA:41,2 +DA:42,1 +DA:43,1 +DA:44,1 +DA:45,1 +DA:46,2 +DA:47,1 +DA:48,1 +DA:49,1 +DA:50,1 +DA:51,1 +DA:54,1 +DA:55,1 +DA:57,2 +DA:60,1 +DA:61,2 +DA:62,4 +DA:63,2 +DA:64,2 +DA:65,1 +DA:69,1 +DA:71,1 +DA:73,0 +DA:86,1 +DA:88,1 +DA:91,1 +DA:92,2 +DA:94,1 +DA:96,2 +DA:97,1 +DA:98,2 +DA:100,1 +DA:101,1 +DA:103,2 +DA:105,2 +DA:107,5 +DA:108,1 +DA:109,1 +DA:110,1 +DA:112,2 +DA:113,3 +DA:115,0 +DA:116,0 +DA:120,1 +DA:121,2 +DA:122,5 +DA:123,3 +DA:125,1 +DA:126,1 +DA:127,2 +DA:129,2 +DA:130,0 +DA:131,1 +DA:132,1 +DA:135,2 +DA:136,0 +DA:137,1 +DA:138,1 +DA:142,1 +DA:144,1 +DA:145,1 +DA:148,2 +DA:149,1 +DA:152,1 +DA:155,1 +DA:156,1 +DA:157,1 +DA:158,0 +DA:159,1 +DA:163,1 +DA:166,1 +DA:167,1 +DA:170,1 +DA:171,1 +DA:172,3 +DA:173,3 +DA:174,1 +DA:177,2 +LF:91 +LH:85 +end_of_record +SF:lib\src\view_models\data_models\single_data_models.dart +DA:23,0 +DA:25,1 +DA:27,1 +DA:28,1 +DA:29,1 +DA:32,3 +DA:34,1 +DA:35,1 +DA:36,1 +DA:38,1 +DA:39,1 +DA:46,1 +DA:47,2 +DA:50,1 +DA:54,1 +DA:57,0 +DA:70,2 +DA:74,1 +DA:76,1 +DA:77,2 +DA:78,1 +DA:81,1 +DA:84,1 +DA:87,1 +DA:89,3 +DA:90,1 +DA:91,1 +DA:92,1 +DA:94,2 +DA:96,2 +DA:97,1 +DA:99,1 +DA:100,1 +DA:101,1 +DA:102,1 +DA:103,1 +DA:107,1 +DA:108,1 +DA:112,0 +DA:115,1 +DA:118,1 +DA:120,0 +DA:123,1 +DA:127,0 +DA:129,0 +DA:130,0 +DA:132,0 +LF:47 +LH:39 +end_of_record +SF:lib\src\view_models\form_view_model.dart +DA:6,0 +DA:7,0 +LF:2 +LH:0 +end_of_record +SF:lib\src\view_models\helpers\index_tracking_state_helper.dart +DA:6,2 +DA:12,2 +DA:14,1 +DA:15,2 +DA:16,1 +DA:18,1 +DA:20,1 +DA:21,1 +DA:24,3 +DA:30,1 +DA:31,2 +DA:36,2 +DA:37,1 +DA:38,5 +DA:40,1 +DA:41,1 +DA:42,4 +DA:43,1 +DA:44,1 +DA:45,1 +DA:46,6 +DA:48,1 +DA:54,2 +DA:56,1 +LF:24 +LH:24 +end_of_record +SF:lib\src\view_models\index_tracking_viewmodel.dart +DA:10,0 +DA:11,0 +LF:2 +LH:0 +end_of_record +SF:lib\src\view_models\selector_view_model_builder.dart +DA:6,0 +DA:12,0 +DA:18,0 +DA:20,0 +DA:21,0 +DA:22,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +LF:10 +LH:0 +end_of_record +SF:lib\src\view_models\selector_view_model_builder_widget.dart +DA:6,0 +DA:9,0 +DA:10,0 +DA:13,0 +DA:16,0 +DA:21,0 +DA:23,0 +DA:25,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:35,0 +DA:36,0 +DA:40,0 +DA:42,0 +DA:43,0 +DA:44,0 +LF:21 +LH:0 +end_of_record +SF:lib\src\view_models\stacked_view.dart +DA:8,0 +DA:14,0 +DA:28,0 +DA:36,0 +DA:41,0 +DA:45,0 +DA:49,0 +DA:54,0 +DA:59,0 +DA:65,0 +DA:71,0 +DA:73,0 +DA:75,0 +DA:76,0 +DA:77,0 +DA:78,0 +DA:79,0 +DA:80,0 +DA:81,0 +DA:82,0 +DA:83,0 +DA:84,0 +DA:85,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:91,0 +DA:92,0 +DA:93,0 +DA:94,0 +DA:95,0 +DA:96,0 +LF:32 +LH:0 +end_of_record +SF:lib\src\view_models\view_model_builder.dart +DA:62,0 +DA:77,0 +DA:80,0 +DA:95,0 +DA:97,0 +DA:98,0 +DA:105,0 +DA:107,0 +DA:109,0 +DA:110,0 +DA:113,0 +DA:114,0 +DA:118,0 +DA:119,0 +DA:121,0 +DA:122,0 +DA:123,0 +DA:124,0 +DA:125,0 +DA:126,0 +DA:130,0 +DA:131,0 +DA:132,0 +DA:133,0 +DA:134,0 +DA:135,0 +DA:136,0 +DA:143,0 +DA:145,0 +DA:146,0 +DA:148,0 +DA:149,0 +DA:151,0 +DA:153,0 +DA:158,0 +DA:159,0 +DA:160,0 +DA:164,0 +DA:166,0 +DA:167,0 +DA:170,0 +DA:172,0 +DA:173,0 +DA:174,0 +DA:175,0 +DA:176,0 +DA:180,0 +DA:181,0 +DA:182,0 +DA:186,0 +DA:187,0 +DA:188,0 +DA:189,0 +DA:190,0 +DA:191,0 +DA:196,0 +DA:197,0 +DA:198,0 +DA:199,0 +DA:200,0 +DA:205,0 +DA:207,0 +DA:208,0 +DA:209,0 +DA:213,0 +DA:218,0 +DA:219,0 +LF:67 +LH:0 +end_of_record +SF:lib\src\view_models\ui\skeleton_loader.dart +DA:18,0 +DA:26,0 +DA:28,0 +DA:29,0 +DA:42,0 +DA:44,0 +DA:45,0 +DA:46,0 +DA:51,0 +DA:53,0 +DA:54,0 +DA:55,0 +DA:56,0 +DA:58,0 +DA:60,0 +DA:61,0 +DA:62,0 +DA:63,0 +DA:64,0 +DA:67,0 +DA:71,0 +DA:74,0 +DA:78,0 +DA:80,0 +DA:86,0 +DA:87,0 +DA:88,0 +DA:89,0 +DA:91,0 +DA:97,0 +DA:98,0 +DA:99,0 +DA:103,0 +DA:104,0 +DA:107,0 +DA:109,0 +DA:110,0 +DA:111,0 +DA:112,0 +DA:113,0 +DA:115,0 +DA:116,0 +DA:117,0 +DA:118,0 +DA:122,0 +DA:126,0 +DA:128,0 +DA:129,0 +DA:130,0 +DA:138,0 +DA:140,0 +DA:142,0 +DA:143,0 +DA:144,0 +DA:145,0 +DA:147,0 +DA:150,0 +DA:152,0 +DA:153,0 +LF:59 +LH:0 +end_of_record +SF:lib\src\view_models\view_model_widget.dart +DA:8,0 +DA:13,0 +DA:14,0 +DA:18,0 +DA:20,0 +DA:21,0 +DA:23,0 +DA:25,0 +DA:27,0 +DA:29,0 +DA:30,0 +DA:31,0 +LF:12 +LH:0 +end_of_record diff --git a/packages/stacked/example/README.md b/example/navigator_example/README.md similarity index 100% rename from packages/stacked/example/README.md rename to example/navigator_example/README.md diff --git a/example/navigator_example/analysis_options.yaml b/example/navigator_example/analysis_options.yaml new file mode 100644 index 000000000..61b6c4de1 --- /dev/null +++ b/example/navigator_example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/stacked/example/android/.gitignore b/example/navigator_example/android/.gitignore similarity index 100% rename from packages/stacked/example/android/.gitignore rename to example/navigator_example/android/.gitignore diff --git a/packages/stacked/example/android/app/build.gradle b/example/navigator_example/android/app/build.gradle similarity index 98% rename from packages/stacked/example/android/app/build.gradle rename to example/navigator_example/android/app/build.gradle index 76752d792..2243edfa1 100644 --- a/packages/stacked/example/android/app/build.gradle +++ b/example/navigator_example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 28 + compileSdkVersion 31 sourceSets { main.java.srcDirs += 'src/main/kotlin' diff --git a/packages/stacked/example/android/app/src/debug/AndroidManifest.xml b/example/navigator_example/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from packages/stacked/example/android/app/src/debug/AndroidManifest.xml rename to example/navigator_example/android/app/src/debug/AndroidManifest.xml diff --git a/packages/stacked_services/example/android/app/src/main/AndroidManifest.xml b/example/navigator_example/android/app/src/main/AndroidManifest.xml similarity index 54% rename from packages/stacked_services/example/android/app/src/main/AndroidManifest.xml rename to example/navigator_example/android/app/src/main/AndroidManifest.xml index 8bc6007d8..09fe11a9d 100644 --- a/packages/stacked_services/example/android/app/src/main/AndroidManifest.xml +++ b/example/navigator_example/android/app/src/main/AndroidManifest.xml @@ -1,21 +1,11 @@ - + - - + + @@ -23,8 +13,6 @@ - + diff --git a/packages/stacked_themes/example/android/app/src/main/kotlin/com/example/themes_example/MainActivity.kt b/example/navigator_example/android/app/src/main/kotlin/com/example/new_architecture/MainActivity.kt similarity index 69% rename from packages/stacked_themes/example/android/app/src/main/kotlin/com/example/themes_example/MainActivity.kt rename to example/navigator_example/android/app/src/main/kotlin/com/example/new_architecture/MainActivity.kt index c8b76eb4f..be03abb41 100644 --- a/packages/stacked_themes/example/android/app/src/main/kotlin/com/example/themes_example/MainActivity.kt +++ b/example/navigator_example/android/app/src/main/kotlin/com/example/new_architecture/MainActivity.kt @@ -1,6 +1,7 @@ -package com.example.themes_example - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} +package com.example.new_architecture + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { + +} diff --git a/packages/stacked/example/android/app/src/main/res/drawable/launch_background.xml b/example/navigator_example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from packages/stacked/example/android/app/src/main/res/drawable/launch_background.xml rename to example/navigator_example/android/app/src/main/res/drawable/launch_background.xml diff --git a/packages/stacked/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/navigator_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/stacked/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to example/navigator_example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/stacked/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/navigator_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/stacked/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to example/navigator_example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/stacked/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/navigator_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/stacked/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to example/navigator_example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/stacked/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/navigator_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/stacked/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to example/navigator_example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/stacked/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/navigator_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/stacked/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to example/navigator_example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/stacked/example/android/app/src/main/res/values/styles.xml b/example/navigator_example/android/app/src/main/res/values/styles.xml similarity index 100% rename from packages/stacked/example/android/app/src/main/res/values/styles.xml rename to example/navigator_example/android/app/src/main/res/values/styles.xml diff --git a/packages/stacked/example/android/app/src/profile/AndroidManifest.xml b/example/navigator_example/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from packages/stacked/example/android/app/src/profile/AndroidManifest.xml rename to example/navigator_example/android/app/src/profile/AndroidManifest.xml diff --git a/packages/stacked/example/android/build.gradle b/example/navigator_example/android/build.gradle similarity index 94% rename from packages/stacked/example/android/build.gradle rename to example/navigator_example/android/build.gradle index 3100ad2d5..7087ad19f 100644 --- a/packages/stacked/example/android/build.gradle +++ b/example/navigator_example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.4.10' repositories { google() jcenter() diff --git a/packages/stacked/example/android/gradle.properties b/example/navigator_example/android/gradle.properties similarity index 100% rename from packages/stacked/example/android/gradle.properties rename to example/navigator_example/android/gradle.properties diff --git a/packages/stacked/example/android/gradle/wrapper/gradle-wrapper.properties b/example/navigator_example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/stacked/example/android/gradle/wrapper/gradle-wrapper.properties rename to example/navigator_example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/stacked/example/android/settings.gradle b/example/navigator_example/android/settings.gradle similarity index 100% rename from packages/stacked/example/android/settings.gradle rename to example/navigator_example/android/settings.gradle diff --git a/example/navigator_example/integration_test/app_test.dart b/example/navigator_example/integration_test/app_test.dart new file mode 100644 index 000000000..561ec54aa --- /dev/null +++ b/example/navigator_example/integration_test/app_test.dart @@ -0,0 +1,306 @@ +import 'package:example/main.dart' as app; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('Integration Test', () { + testWidgets('Ensure all stacked integrations still work as expected', + (WidgetTester tester) async { + // Build our app and trigger a frame. + app.main(); + await tester.pumpAndSettle(); + + // Arrange + + // Home View Widgets + final initialisedText = find.text('Initialised'); + final widgetOne = find.text('Tap to increment'); + final widgetTwo = find.text('Tap to Reset'); + final widgetThree = find.text('Home'); + + // Non Reactive View Widgets + final nonReactiveTitle = find.text('Non Reactive View'); + final goToStreamCounterButton = find.text('Go to stream counter view'); + final title = find.text('This should not change'); + + // Stream Counter View Widgets + final streamCounterTitle = find.text('Stream Counter View'); + final streamSourceButton = find.text('Change Stream Sources'); + final streamSlowSourceText = find.text('Slow'); + final streamFastSourceText = find.text('Fast'); + + // Example Form View Widgets + final exampleFormTitle = find.text('Example Form View'); + final formLoremText = find.text('Lorem'); + final formPasswordTextField = find.byKey(const ValueKey('passwordField')); + final passwordErrorText = find.text('Password should not be empty'); + final dobButton = find.text('Select your Date of birth'); + final date = DateTime(DateTime.now().year, DateTime.now().month, 11); + final dateString = date.toString(); + final dropDownButton = find.byKey(const ValueKey('dropdownField')); + final dropDownItem = find.text('No').last; + final dropDownButtonNo = find.text('No'); + + // Bottom Navigation Bar Widgets + final bottomNavigationBar = find.byType(BottomNavigationBar); + final bottomNavigationBarItemOne = find.text('Favorites'); + final bottomNavigationBarItemTwo = find.text('History'); + final bottomNavigationBarItemThree = find.text('Profile'); + final profileView = find.text('Profile'); + + // General Widget + final fab = find.byType(FloatingActionButton); + final iconButton = find.byType(IconButton); + + // Home View Test + tester.printToConsole('Home view Testing'); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + + expect(initialisedText, findsOneWidget); + expect(widgetOne, findsOneWidget); + expect(widgetTwo, findsOneWidget); + expect(fab, findsOneWidget); + expect(widgetThree, findsNothing); + + // Emulate a tap on the widget one button 3 times. + tester.printToConsole('Emulating tap on widget one button 3 times'); + await tester.tap(widgetOne); + await tester.tap(widgetOne); + await tester.tap(widgetOne); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the widget one and two text has been updated to 3. + tester.printToConsole( + 'Verifying widget one and two text has been updated to 3'); + expect(find.text('3'), findsWidgets); + + // Emulate a tap on the widget two button to reset the counter. + tester.printToConsole('Emulating tap on widget two button'); + await tester.tap(widgetTwo); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the widget one and two text has been updated to 0. + tester.printToConsole( + 'Verifying widget one and two text has been updated to 0'); + expect(find.text('0'), findsWidgets); + + // Emulate a tap on the fab button to navigate to the second view. + tester.printToConsole( + 'Emulating tap on fab button to navigate to the second view'); + await tester.tap(fab); + // Trigger a frame. + await tester.pumpAndSettle(); + + /// Emulating a tap on the back icon button. + await tester.tap(iconButton); + // Trigger a frame. + await tester.pumpAndSettle(); + expect(widgetThree, findsOneWidget); + + await tester.tap(fab); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Non Reactive View Test + tester.printToConsole('Non Reactive view Testing'); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + expect(nonReactiveTitle, findsOneWidget); + expect(goToStreamCounterButton, findsOneWidget); + expect(title, findsOneWidget); + expect(fab, findsOneWidget); + + // Emulate a tap on FAB button + tester.printToConsole('Emulating tap on FAB button'); + await tester.tap(fab); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the title didn't update. + tester.printToConsole('Verify the title didn\'t update'); + expect(title, findsOneWidget); + + // Emulate a tap on the go to stream counter button. + tester.printToConsole('Emulating tap on the go to stream counter button'); + await tester.tap(goToStreamCounterButton); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Stream Counter View Test + tester.printToConsole('Stream Counter view Testing'); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + expect(streamCounterTitle, findsOneWidget); + expect(streamSourceButton, findsOneWidget); + expect(streamSlowSourceText, findsOneWidget); + expect(fab, findsOneWidget); + + // Emulate a tap on the stream source button. + tester.printToConsole('Emulating tap on the stream source button'); + await tester.tap(streamSourceButton); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify it has changed to the fast source. + tester.printToConsole('Verify it has changed to the fast source'); + expect(streamFastSourceText, findsOneWidget); + expect(streamSlowSourceText, findsNothing); + + // Emulate a tap on the back button. + tester.printToConsole('Emulating tap on the back button'); + await tester.pageBack(); + // Trigger a frame. + await tester.pumpAndSettle(); + tester.printToConsole('Navigating back to non reactive view'); + + // Verify the widgets are not present. + tester.printToConsole('Verify the widgets are not present'); + expect(streamCounterTitle, findsNothing); + expect(streamSourceButton, findsNothing); + + // Emulate a tap on the Go to stream counter button. + tester.printToConsole('Emulating tap on the Go to stream counter button'); + await tester.tap(goToStreamCounterButton); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Emulate a tap on the FAB button. + tester.printToConsole('Emulating tap on the FAB button'); + await tester.tap(fab); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Example Form View Test + tester.printToConsole('Example Form View Testing'); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + + expect(exampleFormTitle, findsOneWidget); + expect(formLoremText, findsOneWidget); + expect(formPasswordTextField, findsOneWidget); + expect(dobButton, findsOneWidget); + expect(dropDownButton, findsOneWidget); + + // Emulate a tap on the dob button. + tester.printToConsole('Emulating tap on the dob button'); + await tester.tap(dobButton); + // Trigger a frame. + await tester.pumpAndSettle(); + await tester.tap(find.text('11')); + await tester.tap(find.text('OK')); + // Trigger a frame. + await tester.pump(); + + // Verify the dob button has been updated. + tester.printToConsole('Verify the dob button has been updated'); + expect(find.text(dateString), findsOneWidget); + + // Emulate a tap on the drop down button. + tester.printToConsole('Emulating tap on the drop down button'); + await tester.tap(dropDownButton); + // Trigger a frame. + await tester.pumpAndSettle(); + await tester.tap(dropDownItem); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the drop down button has been updated. + tester.printToConsole('Verify the drop down button has been updated'); + expect(dropDownButtonNo, findsOneWidget); + + // Verify the password text field has been updated to empty string. + tester.printToConsole('Verify the password text field has empty string'); + + expect(find.text('password123'), findsNothing); + //verify error for password is shown when form is submitted and password is empty. + await tester.tap(fab); + await tester.pumpAndSettle(); + expect(passwordErrorText, findsOneWidget); + + // Emulate entering text into the password text field. + tester.printToConsole( + 'Emulating entering text into the password text field'); + await tester.enterText(formPasswordTextField, 'password123'); + + // Emulate navigating to bottom navigation bar view. + tester + .printToConsole('Emulating navigating to bottom navigation bar view'); + await tester.tap(fab); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + expect(bottomNavigationBar, findsOneWidget); + expect(bottomNavigationBarItemOne, findsOneWidget); + expect(bottomNavigationBarItemTwo, findsOneWidget); + expect(bottomNavigationBarItemThree, findsOneWidget); + + // Emulate a tap on the bottom navigation bar item one. + tester.printToConsole( + 'Emulating tap on the bottom navigation bar item one'); + await tester.tap(bottomNavigationBarItemOne); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + expect(find.text('999'), findsOneWidget); + + // Increase the counter. + tester.printToConsole('Increasing the counter'); + await tester.tap(fab); + await tester.tap(fab); + await tester.tap(fab); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the counter has been increased. + tester.printToConsole('Verify the counter has been increased'); + + expect(find.text('1002'), findsOneWidget); + + // Emulate a tap on the bottom navigation bar item two. + tester.printToConsole( + 'Emulating tap on the bottom navigation bar item two'); + await tester.tap(bottomNavigationBarItemTwo); + // Trigger a frame. + await tester.pump(const Duration(milliseconds: 100)); + + // Verify circular progress indicator is present. + tester.printToConsole('Verify circular progress indicator is present'); + expect(find.byType(CircularProgressIndicator), findsOneWidget); + + // Verify after 2 seconds the circular progress indicator is not present. + tester.printToConsole( + 'Verify after 2 seconds the circular progress indicator is not present'); + await tester.pumpAndSettle(const Duration(seconds: 2)); + expect(find.text('100'), findsOneWidget); + + // Emulate a tap on the bottom navigation bar item three. + tester.printToConsole( + 'Emulating tap on the bottom navigation bar item three'); + + await tester.tap(bottomNavigationBarItemThree); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + + expect(profileView, findsWidgets); + + tester.printToConsole('Test completed successfully πŸŽ‰'); + }); + }); +} diff --git a/packages/stacked/example/ios/.gitignore b/example/navigator_example/ios/.gitignore similarity index 100% rename from packages/stacked/example/ios/.gitignore rename to example/navigator_example/ios/.gitignore diff --git a/packages/stacked/example/ios/Flutter/AppFrameworkInfo.plist b/example/navigator_example/ios/Flutter/AppFrameworkInfo.plist similarity index 97% rename from packages/stacked/example/ios/Flutter/AppFrameworkInfo.plist rename to example/navigator_example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..f2872cf47 100644 --- a/packages/stacked/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/navigator_example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 9.0 diff --git a/packages/stacked/example/ios/Flutter/Debug.xcconfig b/example/navigator_example/ios/Flutter/Debug.xcconfig similarity index 100% rename from packages/stacked/example/ios/Flutter/Debug.xcconfig rename to example/navigator_example/ios/Flutter/Debug.xcconfig diff --git a/packages/stacked/example/ios/Flutter/Release.xcconfig b/example/navigator_example/ios/Flutter/Release.xcconfig similarity index 100% rename from packages/stacked/example/ios/Flutter/Release.xcconfig rename to example/navigator_example/ios/Flutter/Release.xcconfig diff --git a/packages/stacked/example/ios/Podfile b/example/navigator_example/ios/Podfile similarity index 100% rename from packages/stacked/example/ios/Podfile rename to example/navigator_example/ios/Podfile diff --git a/packages/stacked_themes/example/ios/Runner.xcodeproj/project.pbxproj b/example/navigator_example/ios/Runner.xcodeproj/project.pbxproj similarity index 85% rename from packages/stacked_themes/example/ios/Runner.xcodeproj/project.pbxproj rename to example/navigator_example/ios/Runner.xcodeproj/project.pbxproj index 359e4f5e0..c616928aa 100644 --- a/packages/stacked_themes/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/navigator_example/ios/Runner.xcodeproj/project.pbxproj @@ -3,14 +3,14 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ + 087D4595D28B86FF54F9941C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE2F0710F9508CE9E267371A /* Pods_Runner.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 7901F357067D13C0C5F3D849 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 20C3573838235716E2BB1F0A /* Pods_Runner.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -30,10 +30,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 13D973FF7A659DE29DFBA53D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 20C3573838235716E2BB1F0A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 3B097939EED35B653D1E5F31 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 377E93FFD476638C7B9A0776 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -45,8 +45,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E1DA0DBF738AEC97C8580D47 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - F0E1EAF317FD2EEFE2C53C5A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + BE2F0710F9508CE9E267371A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CB3C6C5960D76BE5490900A4 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,22 +54,19 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7901F357067D13C0C5F3D849 /* Pods_Runner.framework in Frameworks */, + 087D4595D28B86FF54F9941C /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 39A203EF97B87A62B4E8E043 /* Pods */ = { + 16A60DD6A01716BC8534A1D2 /* Frameworks */ = { isa = PBXGroup; children = ( - F0E1EAF317FD2EEFE2C53C5A /* Pods-Runner.debug.xcconfig */, - 3B097939EED35B653D1E5F31 /* Pods-Runner.release.xcconfig */, - E1DA0DBF738AEC97C8580D47 /* Pods-Runner.profile.xcconfig */, + BE2F0710F9508CE9E267371A /* Pods_Runner.framework */, ); - name = Pods; - path = Pods; + name = Frameworks; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { @@ -89,8 +86,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 39A203EF97B87A62B4E8E043 /* Pods */, - CBFB70BB2E40CEFE33165298 /* Frameworks */, + C7820955A436B53B42DB68D2 /* Pods */, + 16A60DD6A01716BC8534A1D2 /* Frameworks */, ); sourceTree = ""; }; @@ -125,12 +122,15 @@ name = "Supporting Files"; sourceTree = ""; }; - CBFB70BB2E40CEFE33165298 /* Frameworks */ = { + C7820955A436B53B42DB68D2 /* Pods */ = { isa = PBXGroup; children = ( - 20C3573838235716E2BB1F0A /* Pods_Runner.framework */, + 13D973FF7A659DE29DFBA53D /* Pods-Runner.debug.xcconfig */, + 377E93FFD476638C7B9A0776 /* Pods-Runner.release.xcconfig */, + CB3C6C5960D76BE5490900A4 /* Pods-Runner.profile.xcconfig */, ); - name = Frameworks; + name = Pods; + path = Pods; sourceTree = ""; }; /* End PBXGroup section */ @@ -140,14 +140,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 5851D9D11A37204C78ABF3BC /* [CP] Check Pods Manifest.lock */, + A7C09855BCE08D7818924091 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - E1CED503B562335CCEF12E96 /* [CP] Embed Pods Frameworks */, + 60B45487C52EA1C7CCDF405A /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -164,8 +164,8 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; - ORGANIZATIONNAME = ""; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; @@ -174,7 +174,7 @@ }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; + compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -220,26 +220,42 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 5851D9D11A37204C78ABF3BC /* [CP] Check Pods Manifest.lock */ = { + 60B45487C52EA1C7CCDF405A /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseCrashlytics/FirebaseCrashlytics.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework", + "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", + "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", + "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", + "${BUILT_PRODUCTS_DIR}/flutter_statusbarcolor_ns/flutter_statusbarcolor_ns.framework", + "${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework", + "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", + "${BUILT_PRODUCTS_DIR}/shared_preferences_ios/shared_preferences_ios.framework", ); + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCore.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreDiagnostics.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCrashlytics.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_statusbarcolor_ns.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_ios.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { @@ -256,26 +272,26 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - E1CED503B562335CCEF12E96 /* [CP] Embed Pods Frameworks */ = { + A7C09855BCE08D7818924091 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/../Flutter/Flutter.framework", - "${BUILT_PRODUCTS_DIR}/flutter_statusbarcolor/flutter_statusbarcolor.framework", - "${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework", + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_statusbarcolor.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework", + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -353,7 +369,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -375,12 +391,15 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.themesExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.newArchitecture; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -435,7 +454,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -484,7 +503,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -507,12 +526,15 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.themesExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.newArchitecture; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -534,12 +556,15 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.themesExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.newArchitecture; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/packages/stacked/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/navigator_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 71% rename from packages/stacked/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to example/navigator_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/stacked/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/example/navigator_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/stacked/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/navigator_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 99% rename from packages/stacked/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to example/navigator_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..3db53b6e1 100644 --- a/packages/stacked/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/navigator_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + diff --git a/packages/stacked/example/ios/Runner/Runner-Bridging-Header.h b/example/navigator_example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from packages/stacked/example/ios/Runner/Runner-Bridging-Header.h rename to example/navigator_example/ios/Runner/Runner-Bridging-Header.h diff --git a/example/navigator_example/lib/app/app.bottomsheets.dart b/example/navigator_example/lib/app/app.bottomsheets.dart new file mode 100644 index 000000000..df8a623d9 --- /dev/null +++ b/example/navigator_example/lib/app/app.bottomsheets.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedBottomsheetGenerator +// ************************************************************************** + +import 'package:stacked_services/stacked_services.dart'; + +import 'app.locator.dart'; +import '../ui/bottomsheets/generic_bottomsheet.dart'; + +enum BottomSheetType { + genericBottom, +} + +void setupBottomSheetUi() { + final bottomsheetService = exampleLocator(); + + final Map builders = { + BottomSheetType.genericBottom: (context, request, completer) => + GenericBottomSheet(request: request, completer: completer), + }; + + bottomsheetService.setCustomSheetBuilders(builders); +} diff --git a/example/navigator_example/lib/app/app.dart b/example/navigator_example/lib/app/app.dart new file mode 100644 index 000000000..3f8099657 --- /dev/null +++ b/example/navigator_example/lib/app/app.dart @@ -0,0 +1,88 @@ +import 'package:example/services/epoch_service.dart'; +import 'package:example/services/factory_service.dart'; +import 'package:example/services/information_service.dart'; +import 'package:example/ui/bottom_nav/bottom_nav_example.dart'; +import 'package:example/ui/bottom_nav/favorites/favorites_view.dart'; +import 'package:example/ui/bottom_nav/favorites/favorites_viewmodel.dart'; +import 'package:example/ui/bottom_nav/history/history_view.dart'; +import 'package:example/ui/bottom_nav/history/history_viewmodel.dart'; +import 'package:example/ui/bottom_nav/profile/profile_view.dart'; +import 'package:example/ui/bottomsheets/generic_bottomsheet.dart'; +import 'package:example/ui/form/example_form_view.dart'; +import 'package:example/ui/home/home_view.dart'; +import 'package:example/ui/multiple_futures_example/multiple_futures_example_view.dart'; +import 'package:example/ui/nonreactive/nonreactive_view.dart'; +import 'package:example/ui/stream_view/stream_counter_view.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; +import 'package:stacked_shared/stacked_shared.dart'; +import 'package:stacked_themes/stacked_themes.dart'; + +import '../ui/dialogs/basic_dialog.dart'; +import 'custom_route_transition.dart'; + +@StackedApp( + bottomsheets: [ + StackedBottomsheet(classType: GenericBottomSheet), + ], + dialogs: [ + StackedDialog(classType: BasicDialog), + ], + routes: [ + MaterialRoute(page: HomeView, initial: true), + MaterialRoute(page: BottomNavExample, children: [ + CustomRoute( + page: HistoryView, + transitionsBuilder: TransitionsBuilders.fadeIn, + ), + AdaptiveRoute( + page: FavoritesView, + children: [ + MaterialRoute(page: MultipleFuturesExampleView), + CustomRoute(page: HistoryView), + ], + ), + CupertinoRoute(page: ProfileView), + ]), + MaterialRoute(page: StreamCounterView), + MaterialRoute(page: ExampleFormView), + CustomRoute( + page: NonReactiveView, + transitionsBuilder: CustomRouteTransition.sharedAxis, + ), + ], + dependencies: [ + // Lazy singletons + LazySingleton(classType: DialogService), + LazySingleton(classType: BottomSheetService), + // LazySingleton( + // classType: InformationService, + // dispose: disposeInformationService, + // ), + LazySingleton( + classType: NavigationService, + environments: {Environment.dev}, + ), + LazySingleton( + classType: NavigationService, + environments: {Environment.dev}, + instanceName: 'instance1'), + LazySingleton(classType: EpochService), + LazySingleton( + classType: ThemeService, + resolveUsing: ThemeService.getInstance, + ), + LazySingleton(classType: InformationService), + LazySingleton(classType: InformationService, instanceName: 'infoInstance1'), + FactoryWithParam(classType: FactoryService), + // singletons + Singleton(classType: HistoryViewModel), + Singleton(classType: FavoritesViewModel), + ], + logger: StackedLogger(), + locatorName: 'exampleLocator', + locatorSetupName: 'setupExampleLocator', +) +class App { + /// This class has no puporse besides housing the annotation that generates the required functionality +} diff --git a/example/navigator_example/lib/app/app.dialog.dart b/example/navigator_example/lib/app/app.dialog.dart new file mode 100644 index 000000000..4113beba4 --- /dev/null +++ b/example/navigator_example/lib/app/app.dialog.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedDialogGenerator +// ************************************************************************** + +import 'package:stacked_services/stacked_services.dart'; +import 'app.locator.dart'; + +import '../ui/dialogs/basic_dialog.dart'; + +enum DialogType { + basicDialog, +} + +void setupDialogUi() { + var dialogService = exampleLocator(); + + final builders = { + DialogType.basicDialog: (context, DialogRequest request, + void Function(DialogResponse) completer) => + BasicDialog(request: request, completer: completer), + }; + + dialogService.registerCustomDialogBuilders(builders); +} diff --git a/example/navigator_example/lib/app/app.dialogs.dart b/example/navigator_example/lib/app/app.dialogs.dart new file mode 100644 index 000000000..ae7251eba --- /dev/null +++ b/example/navigator_example/lib/app/app.dialogs.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedDialogGenerator +// ************************************************************************** + +import 'package:stacked_services/stacked_services.dart'; + +import 'app.locator.dart'; +import '../ui/dialogs/basic_dialog.dart'; + +enum DialogType { + basic, +} + +void setupDialogUi() { + final dialogService = exampleLocator(); + + final Map builders = { + DialogType.basic: (context, request, completer) => + BasicDialog(request: request, completer: completer), + }; + + dialogService.registerCustomDialogBuilders(builders); +} diff --git a/packages/stacked/example/lib/app/app.locator.dart b/example/navigator_example/lib/app/app.locator.dart similarity index 64% rename from packages/stacked/example/lib/app/app.locator.dart rename to example/navigator_example/lib/app/app.locator.dart index c1fbc4b69..c4c9b215a 100644 --- a/packages/stacked/example/lib/app/app.locator.dart +++ b/example/navigator_example/lib/app/app.locator.dart @@ -4,12 +4,13 @@ // StackedLocatorGenerator // ************************************************************************** -// ignore_for_file: public_member_api_docs +// ignore_for_file: public_member_api_docs, implementation_imports, depend_on_referenced_packages -import 'package:stacked/stacked.dart'; -import 'package:stacked/stacked_annotations.dart'; -import 'package:stacked_services/stacked_services.dart'; -import 'package:stacked_themes/stacked_themes.dart'; +import 'package:stacked_services/src/bottom_sheet/bottom_sheet_service.dart'; +import 'package:stacked_services/src/dialog/dialog_service.dart'; +import 'package:stacked_services/src/navigation/navigation_service.dart'; +import 'package:stacked_shared/stacked_shared.dart'; +import 'package:stacked_themes/src/theme_service.dart'; import '../services/epoch_service.dart'; import '../services/factory_service.dart'; @@ -19,8 +20,10 @@ import '../ui/bottom_nav/history/history_viewmodel.dart'; final exampleLocator = StackedLocator.instance; -void setupExampleLocator( - {String? environment, EnvironmentFilter? environmentFilter}) { +Future setupExampleLocator({ + String? environment, + EnvironmentFilter? environmentFilter, +}) async { // Register environments exampleLocator.registerEnvironment( environment: environment, environmentFilter: environmentFilter); @@ -30,9 +33,13 @@ void setupExampleLocator( exampleLocator.registerLazySingleton(() => BottomSheetService()); exampleLocator .registerLazySingleton(() => NavigationService(), registerFor: {"dev"}); + exampleLocator.registerLazySingleton(() => NavigationService(), + registerFor: {"dev"}, instanceName: 'instance1'); exampleLocator.registerLazySingleton(() => EpochService()); exampleLocator.registerLazySingleton(() => ThemeService.getInstance()); exampleLocator.registerLazySingleton(() => InformationService()); + exampleLocator.registerLazySingleton(() => InformationService(), + instanceName: 'infoInstance1'); exampleLocator.registerFactoryParam( (param1, param2) => FactoryService(param1, data2: param2)); exampleLocator.registerSingleton(HistoryViewModel()); diff --git a/packages/stacked/example/lib/app/app.logger.dart b/example/navigator_example/lib/app/app.logger.dart similarity index 66% rename from packages/stacked/example/lib/app/app.logger.dart rename to example/navigator_example/lib/app/app.logger.dart index 107251ad4..26ab3f947 100644 --- a/packages/stacked/example/lib/app/app.logger.dart +++ b/example/navigator_example/lib/app/app.logger.dart @@ -4,6 +4,8 @@ // StackedLoggerGenerator // ************************************************************************** +// ignore_for_file: avoid_print, depend_on_referenced_packages + /// Maybe this should be generated for the user as well? /// /// import 'package:customer_app/services/stackdriver/stackdriver_service.dart'; @@ -32,10 +34,10 @@ class SimpleLogPrinter extends LogPrinter { var methodName = _getMethodName(); var methodNameSection = - printCallingFunctionName && methodName != null ? ' | $methodName ' : ''; + printCallingFunctionName && methodName != null ? ' | $methodName' : ''; var stackLog = event.stackTrace.toString(); var output = - '$emoji $className$methodNameSection - ${event.message}${printCallStack ? '\nSTACKTRACE:\n$stackLog' : ''}'; + '$emoji $className$methodNameSection - ${event.message}${event.error != null ? '\nERROR: ${event.error}\n' : ''}${printCallStack ? '\nSTACKTRACE:\n$stackLog' : ''}'; if (exludeLogsFromClasses .any((excludeClass) => className == excludeClass) || @@ -59,19 +61,50 @@ class SimpleLogPrinter extends LogPrinter { String? _getMethodName() { try { - var currentStack = StackTrace.current; - var formattedStacktrace = _formatStackTrace(currentStack, 3); - - var realFirstLine = formattedStacktrace - ?.firstWhere((line) => line.contains(className), orElse: () => ""); - - var methodName = realFirstLine?.replaceAll('$className.', ''); - return methodName; + final currentStack = StackTrace.current; + final formattedStacktrace = _formatStackTrace(currentStack, 3); + if (kIsWeb) { + final classNameParts = _splitClassNameWords(className); + return _findMostMatchedTrace(formattedStacktrace!, classNameParts) + .split(' ') + .last; + } else { + final realFirstLine = formattedStacktrace + ?.firstWhere((line) => line.contains(className), orElse: () => ""); + + final methodName = realFirstLine?.replaceAll('$className.', ''); + return methodName; + } } catch (e) { // There's no deliberate function call from our code so we return null; return null; } } + + List _splitClassNameWords(String className) { + return className + .split(RegExp(r'(?=[A-Z])')) + .map((e) => e.toLowerCase()) + .toList(); + } + + /// When the faulty word exists in the begging this method will not be very usefull + String _findMostMatchedTrace( + List stackTraces, List keyWords) { + String match = stackTraces.firstWhere( + (trace) => _doesTraceContainsAllKeywords(trace, keyWords), + orElse: () => ''); + if (match.isEmpty) { + match = _findMostMatchedTrace( + stackTraces, keyWords.sublist(0, keyWords.length - 1)); + } + return match; + } + + bool _doesTraceContainsAllKeywords(String stackTrace, List keywords) { + final formattedKeywordsAsRegex = RegExp(keywords.join('.*')); + return stackTrace.contains(formattedKeywordsAsRegex); + } } final stackTraceRegex = RegExp(r'#[0-9]+[\s]+(.+) \(([^\s]+)\)'); @@ -104,22 +137,6 @@ List? _formatStackTrace(StackTrace stackTrace, int methodCount) { } } -class MultipleLoggerOutput extends LogOutput { - final List logOutputs; - MultipleLoggerOutput(this.logOutputs); - - @override - void output(OutputEvent event) { - for (var logOutput in logOutputs) { - try { - logOutput.output(event); - } catch (e) { - print('Log output failed'); - } - } - } -} - Logger getLogger( String className, { bool printCallingFunctionName = true, @@ -135,7 +152,7 @@ Logger getLogger( showOnlyClass: showOnlyClass, exludeLogsFromClasses: exludeLogsFromClasses, ), - output: MultipleLoggerOutput([ + output: MultiOutput([ if (!kReleaseMode) ConsoleOutput(), ]), ); diff --git a/example/navigator_example/lib/app/app.router.dart b/example/navigator_example/lib/app/app.router.dart new file mode 100644 index 000000000..e8987227a --- /dev/null +++ b/example/navigator_example/lib/app/app.router.dart @@ -0,0 +1,648 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedNavigatorGenerator +// ************************************************************************** + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:example/app/custom_route_transition.dart' as _i9; +import 'package:example/datamodels/clashable_one.dart' as _i10; +import 'package:example/datamodels/clashable_two.dart' as _i11; +import 'package:example/datamodels/home_type.dart' as _i1; +import 'package:example/ui/bottom_nav/bottom_nav_example.dart' as _i4; +import 'package:example/ui/bottom_nav/favorites/favorites_view.dart' as _i13; +import 'package:example/ui/bottom_nav/history/history_view.dart' as _i12; +import 'package:example/ui/bottom_nav/profile/profile_view.dart' as _i14; +import 'package:example/ui/form/example_form_view.dart' as _i6; +import 'package:example/ui/home/home_view.dart' as _i3; +import 'package:example/ui/multiple_futures_example/multiple_futures_example_view.dart' + as _i16; +import 'package:example/ui/nonreactive/nonreactive_view.dart' as _i7; +import 'package:example/ui/stream_view/stream_counter_view.dart' as _i5; +import 'package:flutter/cupertino.dart' as _i15; +import 'package:flutter/material.dart' as _i8; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart' as _i2; +import 'package:stacked_services/stacked_services.dart' as _i17; + +class Routes { + static const homeView = '/'; + + static const bottomNavExample = '/bottom-nav-example'; + + static const streamCounterView = '/stream-counter-view'; + + static const exampleFormView = '/example-form-view'; + + static const nonReactiveView = '/non-reactive-view'; + + static const all = { + homeView, + bottomNavExample, + streamCounterView, + exampleFormView, + nonReactiveView, + }; +} + +class StackedRouter extends _i2.RouterBase { + final _routes = <_i2.RouteDef>[ + _i2.RouteDef( + Routes.homeView, + page: _i3.HomeView, + ), + _i2.RouteDef( + Routes.bottomNavExample, + page: _i4.BottomNavExample, + ), + _i2.RouteDef( + Routes.streamCounterView, + page: _i5.StreamCounterView, + ), + _i2.RouteDef( + Routes.exampleFormView, + page: _i6.ExampleFormView, + ), + _i2.RouteDef( + Routes.nonReactiveView, + page: _i7.NonReactiveView, + ), + ]; + + final _pagesMap = { + _i3.HomeView: (data) { + final args = data.getArgs( + orElse: () => const HomeViewArguments(), + ); + return _i8.MaterialPageRoute( + builder: (context) => _i3.HomeView( + key: args.key, + title: args.title, + isLoggedIn: args.isLoggedIn, + clashableGetter: args.clashableGetter, + homeTypes: args.homeTypes), + settings: data, + maintainState: false, + ); + }, + _i4.BottomNavExample: (data) { + return _i8.MaterialPageRoute( + builder: (context) => const _i4.BottomNavExample(), + settings: data, + maintainState: false, + ); + }, + _i5.StreamCounterView: (data) { + final args = data.getArgs(nullOk: false); + return _i8.MaterialPageRoute( + builder: (context) => _i5.StreamCounterView( + key: args.key, clashableTwo: args.clashableTwo), + settings: data, + maintainState: false, + ); + }, + _i6.ExampleFormView: (data) { + final args = data.getArgs(nullOk: false); + return _i8.MaterialPageRoute( + builder: (context) => + _i6.ExampleFormView(key: args.key, clashableOne: args.clashableOne), + settings: data, + maintainState: false, + ); + }, + _i7.NonReactiveView: (data) { + return _i8.PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => + const _i7.NonReactiveView(), + settings: data, + transitionsBuilder: + data.transition ?? _i9.CustomRouteTransition.sharedAxis, + ); + }, + }; + + @override + List<_i2.RouteDef> get routes => _routes; + @override + Map get pagesMap => _pagesMap; +} + +class HomeViewArguments { + const HomeViewArguments({ + this.key, + this.title = 'hello', + this.isLoggedIn = false, + this.clashableGetter, + this.homeTypes = const [_i1.HomeType.apartment, _i1.HomeType.house], + }); + + final _i8.Key? key; + + final String? title; + + final bool? isLoggedIn; + + final _i10.Clashable Function(String)? clashableGetter; + + final List<_i1.HomeType> homeTypes; + + @override + String toString() { + return '{"key": "$key", "title": "$title", "isLoggedIn": "$isLoggedIn", "clashableGetter": "$clashableGetter", "homeTypes": "$homeTypes"}'; + } +} + +class StreamCounterViewArguments { + const StreamCounterViewArguments({ + this.key, + required this.clashableTwo, + }); + + final _i8.Key? key; + + final List<_i11.Clashable> clashableTwo; + + @override + String toString() { + return '{"key": "$key", "clashableTwo": "$clashableTwo"}'; + } +} + +class ExampleFormViewArguments { + const ExampleFormViewArguments({ + this.key, + required this.clashableOne, + }); + + final _i8.Key? key; + + final _i10.Clashable clashableOne; + + @override + String toString() { + return '{"key": "$key", "clashableOne": "$clashableOne"}'; + } +} + +class BottomNavExampleRoutes { + static const historyView = 'history-view'; + + static const favoritesView = 'favorites-view'; + + static const profileView = 'profile-view'; + + static const all = { + historyView, + favoritesView, + profileView, + }; +} + +class BottomNavExampleRouter extends _i2.RouterBase { + final _routes = <_i2.RouteDef>[ + _i2.RouteDef( + BottomNavExampleRoutes.historyView, + page: _i12.HistoryView, + ), + _i2.RouteDef( + BottomNavExampleRoutes.favoritesView, + page: _i13.FavoritesView, + ), + _i2.RouteDef( + BottomNavExampleRoutes.profileView, + page: _i14.ProfileView, + ), + ]; + + final _pagesMap = { + _i12.HistoryView: (data) { + return _i8.PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => + const _i12.HistoryView(), + settings: data, + transitionsBuilder: data.transition ?? _i2.TransitionsBuilders.fadeIn, + ); + }, + _i13.FavoritesView: (data) { + final args = data.getArgs( + orElse: () => const FavoritesViewArguments(), + ); + return _i2.buildAdaptivePageRoute( + builder: (context) => _i13.FavoritesView(key: args.key, id: args.id), + settings: data, + ); + }, + _i14.ProfileView: (data) { + return _i15.CupertinoPageRoute( + builder: (context) => const _i14.ProfileView(), + settings: data, + maintainState: false, + ); + }, + }; + + @override + List<_i2.RouteDef> get routes => _routes; + @override + Map get pagesMap => _pagesMap; +} + +class FavoritesViewArguments { + const FavoritesViewArguments({ + this.key, + this.id, + }); + + final _i8.Key? key; + + final String? id; + + @override + String toString() { + return '{"key": "$key", "id": "$id"}'; + } +} + +class FavoritesViewRoutes { + static const multipleFuturesExampleView = 'multiple-futures-example-view'; + + static const historyView = 'history-view'; + + static const all = { + multipleFuturesExampleView, + historyView, + }; +} + +class FavoritesViewRouter extends _i2.RouterBase { + final _routes = <_i2.RouteDef>[ + _i2.RouteDef( + FavoritesViewRoutes.multipleFuturesExampleView, + page: _i16.MultipleFuturesExampleView, + ), + _i2.RouteDef( + FavoritesViewRoutes.historyView, + page: _i12.HistoryView, + ), + ]; + + final _pagesMap = { + _i16.MultipleFuturesExampleView: (data) { + return _i8.MaterialPageRoute( + builder: (context) => const _i16.MultipleFuturesExampleView(), + settings: data, + maintainState: false, + ); + }, + _i12.HistoryView: (data) { + return _i8.PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => + const _i12.HistoryView(), + settings: data, + transitionsBuilder: data.transition ?? + (context, animation, secondaryAnimation, child) { + return child; + }, + ); + }, + }; + + @override + List<_i2.RouteDef> get routes => _routes; + @override + Map get pagesMap => _pagesMap; +} + +extension NavigatorStateExtension on _i17.NavigationService { + Future navigateToHomeView({ + _i8.Key? key, + String? title = 'hello', + bool? isLoggedIn = false, + _i10.Clashable Function(String)? clashableGetter, + List<_i1.HomeType> homeTypes = const [ + _i1.HomeType.apartment, + _i1.HomeType.house + ], + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return navigateTo(Routes.homeView, + arguments: HomeViewArguments( + key: key, + title: title, + isLoggedIn: isLoggedIn, + clashableGetter: clashableGetter, + homeTypes: homeTypes), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToBottomNavExample([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(Routes.bottomNavExample, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToStreamCounterView({ + _i8.Key? key, + required List<_i11.Clashable> clashableTwo, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return navigateTo(Routes.streamCounterView, + arguments: + StreamCounterViewArguments(key: key, clashableTwo: clashableTwo), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToExampleFormView({ + _i8.Key? key, + required _i10.Clashable clashableOne, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return navigateTo(Routes.exampleFormView, + arguments: + ExampleFormViewArguments(key: key, clashableOne: clashableOne), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToNonReactiveView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(Routes.nonReactiveView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToNestedHistoryViewInBottomNavExampleRouter([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(BottomNavExampleRoutes.historyView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToFavoritesView({ + _i8.Key? key, + String? id, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return navigateTo(BottomNavExampleRoutes.favoritesView, + arguments: FavoritesViewArguments(key: key, id: id), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToNestedProfileViewInBottomNavExampleRouter([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(BottomNavExampleRoutes.profileView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future + navigateToNestedMultipleFuturesExampleViewInFavoritesViewRouter([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(FavoritesViewRoutes.multipleFuturesExampleView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToNestedHistoryViewInFavoritesViewRouter([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(FavoritesViewRoutes.historyView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithHomeView({ + _i8.Key? key, + String? title = 'hello', + bool? isLoggedIn = false, + _i10.Clashable Function(String)? clashableGetter, + List<_i1.HomeType> homeTypes = const [ + _i1.HomeType.apartment, + _i1.HomeType.house + ], + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return replaceWith(Routes.homeView, + arguments: HomeViewArguments( + key: key, + title: title, + isLoggedIn: isLoggedIn, + clashableGetter: clashableGetter, + homeTypes: homeTypes), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithBottomNavExample([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(Routes.bottomNavExample, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithStreamCounterView({ + _i8.Key? key, + required List<_i11.Clashable> clashableTwo, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return replaceWith(Routes.streamCounterView, + arguments: + StreamCounterViewArguments(key: key, clashableTwo: clashableTwo), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithExampleFormView({ + _i8.Key? key, + required _i10.Clashable clashableOne, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return replaceWith(Routes.exampleFormView, + arguments: + ExampleFormViewArguments(key: key, clashableOne: clashableOne), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithNonReactiveView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(Routes.nonReactiveView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithNestedHistoryViewInBottomNavExampleRouter([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(BottomNavExampleRoutes.historyView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithFavoritesView({ + _i8.Key? key, + String? id, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return replaceWith(BottomNavExampleRoutes.favoritesView, + arguments: FavoritesViewArguments(key: key, id: id), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithNestedProfileViewInBottomNavExampleRouter([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(BottomNavExampleRoutes.profileView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future + replaceWithNestedMultipleFuturesExampleViewInFavoritesViewRouter([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(FavoritesViewRoutes.multipleFuturesExampleView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithNestedHistoryViewInFavoritesViewRouter([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(FavoritesViewRoutes.historyView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } +} diff --git a/example/navigator_example/lib/app/custom_route_transition.dart b/example/navigator_example/lib/app/custom_route_transition.dart new file mode 100644 index 000000000..bc3f6ccf9 --- /dev/null +++ b/example/navigator_example/lib/app/custom_route_transition.dart @@ -0,0 +1,14 @@ +import 'package:animations/animations.dart'; +import 'package:flutter/material.dart'; + +class CustomRouteTransition { + static Widget sharedAxis(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return SharedAxisTransition( + animation: animation, + secondaryAnimation: secondaryAnimation, + transitionType: SharedAxisTransitionType.scaled, + child: child, + ); + } +} diff --git a/example/navigator_example/lib/datamodels/clashable_one.dart b/example/navigator_example/lib/datamodels/clashable_one.dart new file mode 100644 index 000000000..8b0ac1aac --- /dev/null +++ b/example/navigator_example/lib/datamodels/clashable_one.dart @@ -0,0 +1,5 @@ +class Clashable { + final String name; + + const Clashable(this.name); +} diff --git a/example/navigator_example/lib/datamodels/clashable_two.dart b/example/navigator_example/lib/datamodels/clashable_two.dart new file mode 100644 index 000000000..67bfb757f --- /dev/null +++ b/example/navigator_example/lib/datamodels/clashable_two.dart @@ -0,0 +1,5 @@ +class Clashable { + final int age; + + const Clashable(this.age); +} diff --git a/example/navigator_example/lib/datamodels/home_type.dart b/example/navigator_example/lib/datamodels/home_type.dart new file mode 100644 index 000000000..ee39e4bad --- /dev/null +++ b/example/navigator_example/lib/datamodels/home_type.dart @@ -0,0 +1 @@ +enum HomeType { apartment, house } diff --git a/packages/stacked/example/lib/datamodels/human.dart b/example/navigator_example/lib/datamodels/human.dart similarity index 100% rename from packages/stacked/example/lib/datamodels/human.dart rename to example/navigator_example/lib/datamodels/human.dart diff --git a/packages/stacked/example/lib/main.dart b/example/navigator_example/lib/main.dart similarity index 77% rename from packages/stacked/example/lib/main.dart rename to example/navigator_example/lib/main.dart index 0adbf2aa5..cb1ed3e41 100644 --- a/packages/stacked/example/lib/main.dart +++ b/example/navigator_example/lib/main.dart @@ -1,24 +1,25 @@ -// @dart=2.9 - import 'package:flutter/material.dart'; -import 'package:stacked/stacked_annotations.dart'; import 'package:stacked_services/stacked_services.dart'; +import 'package:stacked_shared/stacked_shared.dart'; import 'app/app.locator.dart'; import 'app/app.router.dart'; void main() { setupExampleLocator(environment: Environment.dev); - runApp(MyApp()); + runApp(const MyApp()); } class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', navigatorKey: StackedService.navigatorKey, onGenerateRoute: StackedRouter().onGenerateRoute, + initialRoute: Routes.homeView, theme: ThemeData( primarySwatch: Colors.blue, ), diff --git a/packages/stacked/example/lib/services/epoch_service.dart b/example/navigator_example/lib/services/epoch_service.dart similarity index 85% rename from packages/stacked/example/lib/services/epoch_service.dart rename to example/navigator_example/lib/services/epoch_service.dart index b9fd4fb5d..8fd0e1852 100644 --- a/packages/stacked/example/lib/services/epoch_service.dart +++ b/example/navigator_example/lib/services/epoch_service.dart @@ -1,5 +1,3 @@ -import 'package:new_architecture/services/iepoch_service.dart'; - class EpochService { Stream epochUpdatesNumbers() async* { while (true) { diff --git a/packages/stacked/example/lib/services/factory_service.dart b/example/navigator_example/lib/services/factory_service.dart similarity index 100% rename from packages/stacked/example/lib/services/factory_service.dart rename to example/navigator_example/lib/services/factory_service.dart diff --git a/packages/stacked/example/lib/services/iepoch_service.dart b/example/navigator_example/lib/services/iepoch_service.dart similarity index 100% rename from packages/stacked/example/lib/services/iepoch_service.dart rename to example/navigator_example/lib/services/iepoch_service.dart diff --git a/example/navigator_example/lib/services/information_service.dart b/example/navigator_example/lib/services/information_service.dart new file mode 100644 index 000000000..9bd57f5fc --- /dev/null +++ b/example/navigator_example/lib/services/information_service.dart @@ -0,0 +1,16 @@ +import 'package:stacked/stacked.dart'; + +class InformationService with ListenableServiceMixin { + int _postCount = 0; + int get postCount => _postCount; + + void updatePostCount() { + _postCount++; + notifyListeners(); + } + + void resetCount() { + _postCount = 0; + notifyListeners(); + } +} diff --git a/example/navigator_example/lib/ui/bottom_nav/bottom_nav_example.dart b/example/navigator_example/lib/ui/bottom_nav/bottom_nav_example.dart new file mode 100644 index 000000000..b9b3043a9 --- /dev/null +++ b/example/navigator_example/lib/ui/bottom_nav/bottom_nav_example.dart @@ -0,0 +1,49 @@ +import 'package:example/app/app.router.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; + +import 'bottom_nav_example_viewmodel.dart'; + +class BottomNavExample extends StatefulWidget { + const BottomNavExample({Key? key}) : super(key: key); + + @override + BottomNavExampleState createState() => BottomNavExampleState(); +} + +class BottomNavExampleState extends State { + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + builder: (context, viewModel, child) => Scaffold( + body: ExtendedNavigator( + navigatorKey: StackedService.nestedNavigationKey(1), + initialRoute: BottomNavExampleRoutes.favoritesView, + router: BottomNavExampleRouter(), + ), + bottomNavigationBar: BottomNavigationBar( + elevation: 6, + backgroundColor: Colors.white, + currentIndex: viewModel.currentIndex, + onTap: viewModel.handleNavigation, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.favorite), + label: 'Favorites', + ), + BottomNavigationBarItem( + icon: Icon(Icons.history), + label: 'History', + ), + BottomNavigationBarItem( + icon: Icon(Icons.person), + label: 'Profile', + ), + ], + ), + ), + viewModelBuilder: () => BottomNavExampleViewModel(), + ); + } +} diff --git a/example/navigator_example/lib/ui/bottom_nav/bottom_nav_example_viewmodel.dart b/example/navigator_example/lib/ui/bottom_nav/bottom_nav_example_viewmodel.dart new file mode 100644 index 000000000..92afc53aa --- /dev/null +++ b/example/navigator_example/lib/ui/bottom_nav/bottom_nav_example_viewmodel.dart @@ -0,0 +1,41 @@ +import 'package:example/app/app.locator.dart'; +import 'package:example/app/app.logger.dart'; +import 'package:example/app/app.router.dart'; +import 'package:example/ui/bottom_nav/favorites/favorites_view.dart'; +import 'package:example/ui/bottom_nav/history/history_view.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; + +class BottomNavExampleViewModel extends IndexTrackingViewModel { + final log = getLogger('BottomNavExampleViewModel'); + final _navigationService = exampleLocator(); + + void handleNavigation(int index) { + log.i('handleNavigation: $index'); + setIndex(index); + switch (index) { + case 0: + _navigationService.replaceWithTransition( + const FavoritesView(), + transitionStyle: + reverse ? Transition.rightToLeft : Transition.leftToRight, + id: 1, + ); + break; + case 1: + _navigationService.clearStackAndShowView( + const HistoryView(), + id: 1, + ); + break; + case 2: + _navigationService.pushNamedAndRemoveUntil( + BottomNavExampleRoutes.profileView, + predicate: (route) => route.isFirst, + id: 1, + ); + break; + default: + } + } +} diff --git a/packages/stacked/example/lib/ui/bottom_nav/favorites/favorites_view.dart b/example/navigator_example/lib/ui/bottom_nav/favorites/favorites_view.dart similarity index 69% rename from packages/stacked/example/lib/ui/bottom_nav/favorites/favorites_view.dart rename to example/navigator_example/lib/ui/bottom_nav/favorites/favorites_view.dart index 994abb668..9bb45fa22 100644 --- a/packages/stacked/example/lib/ui/bottom_nav/favorites/favorites_view.dart +++ b/example/navigator_example/lib/ui/bottom_nav/favorites/favorites_view.dart @@ -1,12 +1,12 @@ +import 'package:example/app/app.locator.dart'; import 'package:flutter/material.dart'; -import 'package:new_architecture/app/app.locator.dart'; import 'package:stacked/stacked.dart'; -import '../../../app/app.locator.dart'; import 'favorites_viewmodel.dart'; class FavoritesView extends StatelessWidget { - const FavoritesView({Key? key}) : super(key: key); + final String? id; + const FavoritesView({Key? key, this.id}) : super(key: key); @override Widget build(BuildContext context) { @@ -18,12 +18,12 @@ class FavoritesView extends StatelessWidget { body: Center( child: Text( viewModel.counter.toString(), - style: TextStyle(fontSize: 30), + style: const TextStyle(fontSize: 30), ))), viewModelBuilder: () => exampleLocator(), - onModelReady: (viewModel) => viewModel.setCounterTo999(), + onViewModelReady: (viewModel) => viewModel.setCounterTo999(), disposeViewModel: false, - fireOnModelReadyOnce: true, + fireOnViewModelReadyOnce: true, ); } } diff --git a/packages/stacked/example/lib/ui/bottom_nav/favorites/favorites_viewmodel.dart b/example/navigator_example/lib/ui/bottom_nav/favorites/favorites_viewmodel.dart similarity index 98% rename from packages/stacked/example/lib/ui/bottom_nav/favorites/favorites_viewmodel.dart rename to example/navigator_example/lib/ui/bottom_nav/favorites/favorites_viewmodel.dart index c6aa05962..0c50eb4ca 100644 --- a/packages/stacked/example/lib/ui/bottom_nav/favorites/favorites_viewmodel.dart +++ b/example/navigator_example/lib/ui/bottom_nav/favorites/favorites_viewmodel.dart @@ -1,10 +1,9 @@ import 'package:stacked/stacked.dart'; class FavoritesViewModel extends BaseViewModel { - - int _counter = 0; int get counter => _counter; + void incrementCounter() { _counter++; notifyListeners(); diff --git a/packages/stacked/example/lib/ui/bottom_nav/history/history_view.dart b/example/navigator_example/lib/ui/bottom_nav/history/history_view.dart similarity index 81% rename from packages/stacked/example/lib/ui/bottom_nav/history/history_view.dart rename to example/navigator_example/lib/ui/bottom_nav/history/history_view.dart index c239991b0..608a7c6c7 100644 --- a/packages/stacked/example/lib/ui/bottom_nav/history/history_view.dart +++ b/example/navigator_example/lib/ui/bottom_nav/history/history_view.dart @@ -1,8 +1,7 @@ +import 'package:example/app/app.locator.dart'; import 'package:flutter/material.dart'; -import 'package:new_architecture/app/app.locator.dart'; import 'package:stacked/stacked.dart'; -import '../../../app/app.locator.dart'; import 'history_viewmodel.dart'; class HistoryView extends StatelessWidget { @@ -16,7 +15,7 @@ class HistoryView extends StatelessWidget { builder: (context, viewModel, child) => Scaffold( body: Center( child: viewModel.isBusy - ? CircularProgressIndicator() + ? const CircularProgressIndicator() : Text(viewModel.data.toString()))), viewModelBuilder: () => exampleLocator(), ); diff --git a/packages/stacked/example/lib/ui/bottom_nav/history/history_viewmodel.dart b/example/navigator_example/lib/ui/bottom_nav/history/history_viewmodel.dart similarity index 75% rename from packages/stacked/example/lib/ui/bottom_nav/history/history_viewmodel.dart rename to example/navigator_example/lib/ui/bottom_nav/history/history_viewmodel.dart index c9d093f57..092637e13 100644 --- a/packages/stacked/example/lib/ui/bottom_nav/history/history_viewmodel.dart +++ b/example/navigator_example/lib/ui/bottom_nav/history/history_viewmodel.dart @@ -3,7 +3,7 @@ import 'package:stacked/stacked.dart'; class HistoryViewModel extends FutureViewModel { @override Future futureToRun() async { - await Future.delayed(Duration(seconds: 2)); + await Future.delayed(const Duration(seconds: 2)); return 100; } } diff --git a/packages/stacked/example/lib/ui/bottom_nav/profile/profile_view.dart b/example/navigator_example/lib/ui/bottom_nav/profile/profile_view.dart similarity index 86% rename from packages/stacked/example/lib/ui/bottom_nav/profile/profile_view.dart rename to example/navigator_example/lib/ui/bottom_nav/profile/profile_view.dart index 68aa67109..09ceb6782 100644 --- a/packages/stacked/example/lib/ui/bottom_nav/profile/profile_view.dart +++ b/example/navigator_example/lib/ui/bottom_nav/profile/profile_view.dart @@ -10,7 +10,7 @@ class ProfileView extends StatelessWidget { Widget build(BuildContext context) { return ViewModelBuilder.reactive( builder: (context, viewModel, child) => - Scaffold(body: Center(child: Text('History'))), + const Scaffold(body: Center(child: Text('Profile'))), viewModelBuilder: () => ProfileViewModel(), ); } diff --git a/packages/stacked/example/lib/ui/bottom_nav/profile/profile_viewmodel.dart b/example/navigator_example/lib/ui/bottom_nav/profile/profile_viewmodel.dart similarity index 100% rename from packages/stacked/example/lib/ui/bottom_nav/profile/profile_viewmodel.dart rename to example/navigator_example/lib/ui/bottom_nav/profile/profile_viewmodel.dart diff --git a/example/navigator_example/lib/ui/bottomsheets/generic_bottomsheet.dart b/example/navigator_example/lib/ui/bottomsheets/generic_bottomsheet.dart new file mode 100644 index 000000000..197eeb862 --- /dev/null +++ b/example/navigator_example/lib/ui/bottomsheets/generic_bottomsheet.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:stacked_services/stacked_services.dart'; + +class GenericBottomSheet extends StatelessWidget { + final SheetRequest request; + final Function(SheetResponse) completer; + + const GenericBottomSheet({ + Key? key, + required this.request, + required this.completer, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.all(25), + padding: const EdgeInsets.all(25), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + request.title ?? 'Generic Bottom Sheet', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.grey[900], + ), + ), + const SizedBox(height: 10), + Text( + request.description ?? '', + style: const TextStyle(color: Colors.grey), + ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MaterialButton( + onPressed: () => completer(SheetResponse( + confirmed: true, + data: const GenericBottomSheetResponse( + message: 'SecondaryButton'), + )), + child: Text( + request.secondaryButtonTitle ?? '', + style: TextStyle(color: Theme.of(context).primaryColor), + ), + ), + TextButton( + onPressed: () => completer(SheetResponse( + confirmed: true, + data: const GenericBottomSheetResponse(message: 'MainButton'), + )), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Theme.of(context).primaryColor, + ), + ), + child: Text( + request.mainButtonTitle ?? '', + style: const TextStyle(color: Colors.white), + ), + ) + ], + ) + ], + ), + ); + } +} + +class GenericBottomSheetResponse { + const GenericBottomSheetResponse({ + this.message = 'GenericBottomSheetResponse', + }); + + final String message; +} diff --git a/packages/stacked/example/lib/ui/builder_widget_example/builder_widget_example_view.dart b/example/navigator_example/lib/ui/builder_widget_example/builder_widget_example_view.dart similarity index 59% rename from packages/stacked/example/lib/ui/builder_widget_example/builder_widget_example_view.dart rename to example/navigator_example/lib/ui/builder_widget_example/builder_widget_example_view.dart index f40735629..42d9d0721 100644 --- a/packages/stacked/example/lib/ui/builder_widget_example/builder_widget_example_view.dart +++ b/example/navigator_example/lib/ui/builder_widget_example/builder_widget_example_view.dart @@ -1,14 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:new_architecture/ui/home/home_viewmodel.dart'; +import 'package:example/ui/home/home_viewmodel.dart'; import 'package:stacked/stacked.dart'; -class BuilderWidgetExampleView extends ViewModelBuilderWidget { +class BuilderWidgetExampleView extends StackedView { + const BuilderWidgetExampleView({Key? key}) : super(key: key); + @override - Widget builder( - BuildContext context, - HomeViewModel viewModel, - Widget? child, - ) { + Widget builder(BuildContext context, HomeViewModel viewModel, Widget? child) { return Scaffold( body: Center( child: Text(viewModel.title), diff --git a/packages/stacked/example/lib/ui/builder_widget_example/builder_widget_example_viewmodel.dart b/example/navigator_example/lib/ui/builder_widget_example/builder_widget_example_viewmodel.dart similarity index 100% rename from packages/stacked/example/lib/ui/builder_widget_example/builder_widget_example_viewmodel.dart rename to example/navigator_example/lib/ui/builder_widget_example/builder_widget_example_viewmodel.dart diff --git a/example/navigator_example/lib/ui/dialogs/basic_dialog.dart b/example/navigator_example/lib/ui/dialogs/basic_dialog.dart new file mode 100644 index 000000000..d8ed24f5c --- /dev/null +++ b/example/navigator_example/lib/ui/dialogs/basic_dialog.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:stacked_services/stacked_services.dart'; + +class BasicDialog extends StatelessWidget { + final DialogRequest request; + final void Function(DialogResponse) completer; + const BasicDialog({ + Key? key, + required this.request, + required this.completer, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return const SizedBox(); + } +} diff --git a/packages/stacked/example/lib/ui/drop_down_menu_from/select_location_view.dart b/example/navigator_example/lib/ui/drop_down_menu_from/select_location_view.dart similarity index 82% rename from packages/stacked/example/lib/ui/drop_down_menu_from/select_location_view.dart rename to example/navigator_example/lib/ui/drop_down_menu_from/select_location_view.dart index 152f4bbf2..0e15e55d9 100644 --- a/packages/stacked/example/lib/ui/drop_down_menu_from/select_location_view.dart +++ b/example/navigator_example/lib/ui/drop_down_menu_from/select_location_view.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; -import 'package:stacked/stacked_annotations.dart'; +import 'package:stacked_shared/stacked_shared.dart'; -import 'select_location_viewmodel.dart'; import 'select_location_view.form.dart'; +import 'select_location_viewmodel.dart'; @FormView(fields: [ FormDropdownField(name: 'country', items: [ @@ -56,30 +56,32 @@ import 'select_location_view.form.dart'; ]), ]) class SelectLocationView extends StatelessWidget with $SelectLocationView { + const SelectLocationView({super.key}); + @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( - onModelReady: (model) { - listenToFormUpdated(model); + onViewModelReady: (model) { + syncFormWithViewModel(model); model.setCountry(CountryValueToTitleMap.keys.first); model.setProvince(ProvinceValueToTitleMap.keys.first); }, viewModelBuilder: () => SelectLocationViewModel(), builder: (context, model, child) => Scaffold( - backgroundColor: Color.fromARGB(255, 26, 27, 30), + backgroundColor: const Color.fromARGB(255, 26, 27, 30), body: Center( child: Padding( - padding: EdgeInsets.symmetric(horizontal: 20), + padding: const EdgeInsets.symmetric(horizontal: 20), child: Column(mainAxisSize: MainAxisSize.min, children: [ - Text("Select Location", + const Text("Select Location", style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.w800)), - Text("Manually select your location"), - SizedBox(height: 20), + const Text("Manually select your location"), + const SizedBox(height: 20), Row( children: [ - Text('Country'), - SizedBox(width: 15), + const Text('Country'), + const SizedBox(width: 15), DropdownButton( value: model.countryValue, onChanged: (value) { @@ -96,11 +98,11 @@ class SelectLocationView extends StatelessWidget with $SelectLocationView { ), ], ), - SizedBox(height: 10), + const SizedBox(height: 10), Row( children: [ - Text('State / Province'), - SizedBox(width: 15), + const Text('State / Province'), + const SizedBox(width: 15), DropdownButton( value: model.provinceValue, onChanged: (value) { @@ -117,7 +119,7 @@ class SelectLocationView extends StatelessWidget with $SelectLocationView { ), ], ), - SizedBox(height: 10), + const SizedBox(height: 10), ]), ), ), diff --git a/packages/stacked/example/lib/ui/drop_down_menu_from/select_location_view.form.dart b/example/navigator_example/lib/ui/drop_down_menu_from/select_location_view.form.dart similarity index 78% rename from packages/stacked/example/lib/ui/drop_down_menu_from/select_location_view.form.dart rename to example/navigator_example/lib/ui/drop_down_menu_from/select_location_view.form.dart index 66685d998..abfca4e5c 100644 --- a/packages/stacked/example/lib/ui/drop_down_menu_from/select_location_view.form.dart +++ b/example/navigator_example/lib/ui/drop_down_menu_from/select_location_view.form.dart @@ -4,7 +4,7 @@ // StackedFormGenerator // ************************************************************************** -// ignore_for_file: public_member_api_docs +// ignore_for_file: public_member_api_docs, constant_identifier_names, non_constant_identifier_names,unnecessary_this import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; @@ -12,11 +12,11 @@ import 'package:stacked/stacked.dart'; const String CountryValueKey = 'country'; const String ProvinceValueKey = 'province'; -const Map CountryValueToTitleMap = { +final Map CountryValueToTitleMap = { 'ZAR': 'South Africa', 'UK': 'United Kingdom', }; -const Map ProvinceValueToTitleMap = { +final Map ProvinceValueToTitleMap = { 'Western Cape': 'Western Cape', 'Easter Cape': 'Easter Cape', 'Free State': 'Free State', @@ -31,12 +31,13 @@ const Map ProvinceValueToTitleMap = { mixin $SelectLocationView on StatelessWidget { /// Registers a listener on every generated controller that calls [model.setData()] /// with the latest textController values - void listenToFormUpdated(FormViewModel model) {} + void syncFormWithViewModel(FormViewModel model) {} - /// Updates the formData on the FormViewModel - void _updateFormData(FormViewModel model) => model.setData( - model.formValueMap..addAll({}), - ); + /// Registers a listener on every generated controller that calls [model.setData()] + /// with the latest textController values + @Deprecated('Use syncFormWithViewModel instead.' + 'This feature was deprecated after 3.1.0.') + void listenToFormUpdated(FormViewModel model) {} /// Calls dispose on all the generated controllers and focus nodes void disposeForm() { @@ -45,8 +46,10 @@ mixin $SelectLocationView on StatelessWidget { } extension ValueProperties on FormViewModel { - String? get countryValue => this.formValueMap[CountryValueKey]; - String? get provinceValue => this.formValueMap[ProvinceValueKey]; + bool get isFormValid => + this.fieldsValidationMessages.values.every((element) => element == null); + String? get countryValue => this.formValueMap[CountryValueKey] as String?; + String? get provinceValue => this.formValueMap[ProvinceValueKey] as String?; bool get hasCountry => this.formValueMap.containsKey(CountryValueKey); bool get hasProvince => this.formValueMap.containsKey(ProvinceValueKey); @@ -60,6 +63,7 @@ extension ValueProperties on FormViewModel { this.fieldsValidationMessages[CountryValueKey]; String? get provinceValidationMessage => this.fieldsValidationMessages[ProvinceValueKey]; + void clearForm() {} } extension Methods on FormViewModel { diff --git a/packages/stacked/example/lib/ui/drop_down_menu_from/select_location_viewmodel.dart b/example/navigator_example/lib/ui/drop_down_menu_from/select_location_viewmodel.dart similarity index 60% rename from packages/stacked/example/lib/ui/drop_down_menu_from/select_location_viewmodel.dart rename to example/navigator_example/lib/ui/drop_down_menu_from/select_location_viewmodel.dart index b7b46f56e..7c91b9f6f 100644 --- a/packages/stacked/example/lib/ui/drop_down_menu_from/select_location_viewmodel.dart +++ b/example/navigator_example/lib/ui/drop_down_menu_from/select_location_viewmodel.dart @@ -2,13 +2,9 @@ import 'package:stacked/stacked.dart'; import '../../app/app.logger.dart'; -const String SelectLocationViewName = 'SelectLocationView'; - class SelectLocationViewModel extends FormViewModel { var log = getLogger('SelectLocationViewModel'); @override - void setFormStatus() { - // TODO: implement setFormStatus - } + void setFormStatus() {} } diff --git a/packages/stacked/example/lib/ui/dumb_widgets/description_section.dart b/example/navigator_example/lib/ui/dumb_widgets/description_section.dart similarity index 70% rename from packages/stacked/example/lib/ui/dumb_widgets/description_section.dart rename to example/navigator_example/lib/ui/dumb_widgets/description_section.dart index 5ef50edbb..daa8cce6a 100644 --- a/packages/stacked/example/lib/ui/dumb_widgets/description_section.dart +++ b/example/navigator_example/lib/ui/dumb_widgets/description_section.dart @@ -1,19 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:new_architecture/ui/home/home_viewmodel.dart'; +import 'package:example/ui/home/home_viewmodel.dart'; import 'package:stacked/stacked.dart'; class DescriptionSection extends ViewModelWidget { + const DescriptionSection({Key? key}) : super(key: key); + @override Widget build(BuildContext context, HomeViewModel viewModel) { return Row( children: [ - Text( + const Text( 'Description', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700), ), - Container( - child: Text(viewModel.title), - ), + Text(viewModel.title), ], ); } diff --git a/example/navigator_example/lib/ui/dumb_widgets/duplicate_name_widget.dart b/example/navigator_example/lib/ui/dumb_widgets/duplicate_name_widget.dart new file mode 100644 index 000000000..4f95b4c3d --- /dev/null +++ b/example/navigator_example/lib/ui/dumb_widgets/duplicate_name_widget.dart @@ -0,0 +1,20 @@ +import 'package:example/datamodels/human.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +class DuplicateNameWidget extends ViewModelWidget { + const DuplicateNameWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, Human viewModel) { + return Row( + children: [ + Text(viewModel.name!), + const SizedBox( + width: 50, + ), + Text(viewModel.name!), + ], + ); + } +} diff --git a/example/navigator_example/lib/ui/dumb_widgets/full_name_widget.dart b/example/navigator_example/lib/ui/dumb_widgets/full_name_widget.dart new file mode 100644 index 000000000..8f8c5531d --- /dev/null +++ b/example/navigator_example/lib/ui/dumb_widgets/full_name_widget.dart @@ -0,0 +1,26 @@ +import 'package:example/datamodels/human.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +class FullNameWidget extends ViewModelWidget { + const FullNameWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, Human viewModel) { + return Row( + children: [ + Text( + viewModel.name!, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 30), + ), + const SizedBox( + width: 50, + ), + Text( + viewModel.surname!, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 30), + ), + ], + ); + } +} diff --git a/packages/stacked/example/lib/ui/dumb_widgets/title_section.dart b/example/navigator_example/lib/ui/dumb_widgets/title_section.dart similarity index 69% rename from packages/stacked/example/lib/ui/dumb_widgets/title_section.dart rename to example/navigator_example/lib/ui/dumb_widgets/title_section.dart index 7049436c0..ed25e07d0 100644 --- a/packages/stacked/example/lib/ui/dumb_widgets/title_section.dart +++ b/example/navigator_example/lib/ui/dumb_widgets/title_section.dart @@ -1,19 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:new_architecture/ui/home/home_viewmodel.dart'; +import 'package:example/ui/home/home_viewmodel.dart'; import 'package:stacked/stacked.dart'; class TitleSection extends ViewModelWidget { + const TitleSection({Key? key}) : super(key: key); + @override Widget build(BuildContext context, HomeViewModel viewModel) { return Row( children: [ - Text( + const Text( 'Title', style: TextStyle(fontSize: 20), ), - Container( - child: Text(viewModel.title), - ), + Text(viewModel.title), ], ); } diff --git a/example/navigator_example/lib/ui/form/custom_text_field.dart b/example/navigator_example/lib/ui/form/custom_text_field.dart new file mode 100644 index 000000000..762c428bb --- /dev/null +++ b/example/navigator_example/lib/ui/form/custom_text_field.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +class CustomEditingController extends TextEditingController { + static CustomEditingController getCustomEditingController() { + return CustomEditingController(); + } +} diff --git a/example/navigator_example/lib/ui/form/example_form_view.dart b/example/navigator_example/lib/ui/form/example_form_view.dart new file mode 100644 index 000000000..1ad96a3f4 --- /dev/null +++ b/example/navigator_example/lib/ui/form/example_form_view.dart @@ -0,0 +1,189 @@ +import 'package:example/datamodels/clashable_one.dart'; +import 'package:example/ui/form/custom_text_field.dart'; +import 'package:example/ui/form/validators.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_shared/stacked_shared.dart'; + +import 'example_form_view.form.dart'; +import 'example_form_viewmodel.dart'; + +// #1: Add the annotation +@FormView( + fields: [ + FormTextField( + name: 'email', + initialValue: "Lorem", + validator: FormValidators.emailValidator, + ), + FormTextField( + name: 'password', + validator: FormValidators.passwordValidator, + customTextEditingController: + CustomEditingController.getCustomEditingController), + FormTextField(name: 'shortBio'), + FormDateField(name: 'birthDate'), + FormDropdownField( + name: 'doYouLoveFood', + items: [ + StaticDropdownItem( + title: 'Yes', + value: 'YesDr', + ), + StaticDropdownItem( + title: 'No', + value: 'NoDr', + ), + ], + ) + ], + autoTextFieldValidation: false, +) +// #2: with $ExampleFormView +class ExampleFormView extends StatelessWidget with $ExampleFormView { + final Clashable clashableOne; + ExampleFormView({Key? key, required this.clashableOne}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + onViewModelReady: (viewModel) { + // #3: Listen to text updates by calling listenToFormUpdated(model); + syncFormWithViewModel(viewModel); + DoYouLoveFoodValueToTitleMap.addAll({'MaybeDr': 'Maybe'}); + viewModel.setDoYouLoveFood(DoYouLoveFoodValueToTitleMap.keys.first); + }, + onDispose: (model) => disposeForm(), + builder: (context, viewModel, child) => Scaffold( + appBar: AppBar( + title: const Text('Example Form View'), + centerTitle: true, + ), + floatingActionButton: FloatingActionButton(onPressed: () { + if (validateFormFields(viewModel)) { + viewModel.navigateToNewView(); + } + }), + body: SingleChildScrollView( + child: SizedBox( + width: MediaQuery.of(context).size.width, + child: Form( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 300, + ), + child: TextFormField( + //#4: Set email emailController and focus node + controller: emailController, + decoration: const InputDecoration(hintText: 'email'), + keyboardType: TextInputType.emailAddress, + focusNode: emailFocusNode, + ), + ), + Visibility( + visible: viewModel.hasEmailValidationMessage, + child: Text( + viewModel.emailValidationMessage ?? '', + style: const TextStyle(color: Colors.red), + ), + ), + const SizedBox(height: 15), + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 300, + ), + child: TextFormField( + //#5: Set password passwordController and focus node + key: const ValueKey('passwordField'), + controller: passwordController, + decoration: const InputDecoration(hintText: 'password'), + keyboardType: TextInputType.visiblePassword, + obscureText: true, + focusNode: passwordFocusNode, + onFieldSubmitted: (_) => viewModel.saveData(), + ), + ), + Visibility( + visible: viewModel.hasPasswordValidationMessage, + child: Text( + viewModel.passwordValidationMessage ?? '', + style: const TextStyle(color: Colors.red), + ), + ), + const SizedBox(height: 15), + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 300, + ), + child: TextField( + //#6: Set shortBio shortBioController and focus node + maxLines: null, + keyboardType: TextInputType.multiline, + controller: shortBioController, + decoration: const InputDecoration( + hintText: 'Tell us a bit more about yourself', + ), + focusNode: shortBioFocusNode, + ), + ), + const SizedBox(height: 15), + ElevatedButton( + onPressed: () => viewModel.selectBirthDate( + context: context, + firstDate: DateTime.now() + .subtract(const Duration(days: 365 * 5)), + initialDate: DateTime.now(), + lastDate: + DateTime.now().add(const Duration(days: 365 * 5))), + child: Text( + viewModel.hasBirthDate + ? viewModel.birthDateValue.toString() + : 'Select your Date of birth', + ), + ), + const SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Do you love food?'), + const SizedBox(width: 15), + DropdownButton( + key: const ValueKey('dropdownField'), + value: viewModel.doYouLoveFoodValue, + onChanged: (value) { + viewModel.setDoYouLoveFood(value!); + }, + items: DoYouLoveFoodValueToTitleMap.keys + .map( + (value) => DropdownMenuItem( + key: ValueKey('$value key'), + value: value, + child: + Text(DoYouLoveFoodValueToTitleMap[value]!), + ), + ) + .toList(), + ) + ], + ), + const SizedBox(height: 15), + Visibility( + visible: viewModel.showValidationMessage, + child: Text( + viewModel.validationMessage ?? '', + style: const TextStyle(color: Colors.red), + ), + ), + ], + ), + ), + ), + ), + ), + viewModelBuilder: () => ExampleFormViewModel(), + ); + } +} diff --git a/example/navigator_example/lib/ui/form/example_form_view.form.dart b/example/navigator_example/lib/ui/form/example_form_view.form.dart new file mode 100644 index 000000000..1ecd6e167 --- /dev/null +++ b/example/navigator_example/lib/ui/form/example_form_view.form.dart @@ -0,0 +1,277 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedFormGenerator +// ************************************************************************** + +// ignore_for_file: public_member_api_docs, constant_identifier_names, non_constant_identifier_names,unnecessary_this + +import 'package:example/ui/form/custom_text_field.dart'; +import 'package:example/ui/form/validators.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +const String EmailValueKey = 'email'; +const String PasswordValueKey = 'password'; +const String ShortBioValueKey = 'shortBio'; +const String BirthDateValueKey = 'birthDate'; +const String DoYouLoveFoodValueKey = 'doYouLoveFood'; + +final Map DoYouLoveFoodValueToTitleMap = { + 'YesDr': 'Yes', + 'NoDr': 'No', +}; + +final Map + _ExampleFormViewTextEditingControllers = {}; + +final Map _ExampleFormViewFocusNodes = {}; + +final Map _ExampleFormViewTextValidations = + { + EmailValueKey: FormValidators.emailValidator, + PasswordValueKey: FormValidators.passwordValidator, + ShortBioValueKey: null, +}; + +mixin $ExampleFormView on StatelessWidget { + TextEditingController get emailController => + _getFormTextEditingController(EmailValueKey, initialValue: 'Lorem'); + CustomEditingController get passwordController => + _getCustomFormTextEditingController(PasswordValueKey); + TextEditingController get shortBioController => + _getFormTextEditingController(ShortBioValueKey); + + CustomEditingController _getCustomFormTextEditingController( + String key, + ) { + if (_ExampleFormViewTextEditingControllers.containsKey(key)) { + return _ExampleFormViewTextEditingControllers[key]! + as CustomEditingController; + } + _ExampleFormViewTextEditingControllers[key] = + CustomEditingController.getCustomEditingController(); + return _ExampleFormViewTextEditingControllers[key]! + as CustomEditingController; + } + + FocusNode get emailFocusNode => _getFormFocusNode(EmailValueKey); + FocusNode get passwordFocusNode => _getFormFocusNode(PasswordValueKey); + FocusNode get shortBioFocusNode => _getFormFocusNode(ShortBioValueKey); + + TextEditingController _getFormTextEditingController(String key, + {String? initialValue}) { + if (_ExampleFormViewTextEditingControllers.containsKey(key)) { + return _ExampleFormViewTextEditingControllers[key]!; + } + _ExampleFormViewTextEditingControllers[key] = + TextEditingController(text: initialValue); + return _ExampleFormViewTextEditingControllers[key]!; + } + + FocusNode _getFormFocusNode(String key) { + if (_ExampleFormViewFocusNodes.containsKey(key)) { + return _ExampleFormViewFocusNodes[key]!; + } + _ExampleFormViewFocusNodes[key] = FocusNode(); + return _ExampleFormViewFocusNodes[key]!; + } + + /// Registers a listener on every generated controller that calls [model.setData()] + /// with the latest textController values + void syncFormWithViewModel(FormViewModel model) { + emailController.addListener(() => _updateFormData(model)); + passwordController.addListener(() => _updateFormData(model)); + shortBioController.addListener(() => _updateFormData(model)); + } + + /// Registers a listener on every generated controller that calls [model.setData()] + /// with the latest textController values + @Deprecated('Use syncFormWithViewModel instead.' + 'This feature was deprecated after 3.1.0.') + void listenToFormUpdated(FormViewModel model) { + emailController.addListener(() => _updateFormData(model)); + passwordController.addListener(() => _updateFormData(model)); + shortBioController.addListener(() => _updateFormData(model)); + } + + final bool _autoTextFieldValidation = false; + bool validateFormFields(FormViewModel model) { + _updateFormData(model, forceValidate: true); + return model.isFormValid; + } + + /// Updates the formData on the FormViewModel + void _updateFormData(FormViewModel model, {bool forceValidate = false}) { + model.setData( + model.formValueMap + ..addAll({ + EmailValueKey: emailController.text, + PasswordValueKey: passwordController.text, + ShortBioValueKey: shortBioController.text, + }), + ); + if (_autoTextFieldValidation || forceValidate) { + _updateValidationData(model); + } + } + + /// Updates the fieldsValidationMessages on the FormViewModel + void _updateValidationData(FormViewModel model) => + model.setValidationMessages({ + EmailValueKey: _getValidationMessage(EmailValueKey), + PasswordValueKey: _getValidationMessage(PasswordValueKey), + ShortBioValueKey: _getValidationMessage(ShortBioValueKey), + }); + + /// Returns the validation message for the given key + String? _getValidationMessage(String key) { + final validatorForKey = _ExampleFormViewTextValidations[key]; + if (validatorForKey == null) return null; + String? validationMessageForKey = + validatorForKey(_ExampleFormViewTextEditingControllers[key]!.text); + return validationMessageForKey; + } + + /// Calls dispose on all the generated controllers and focus nodes + void disposeForm() { + // The dispose function for a TextEditingController sets all listeners to null + + for (var controller in _ExampleFormViewTextEditingControllers.values) { + controller.dispose(); + } + for (var focusNode in _ExampleFormViewFocusNodes.values) { + focusNode.dispose(); + } + + _ExampleFormViewTextEditingControllers.clear(); + _ExampleFormViewFocusNodes.clear(); + } +} + +extension ValueProperties on FormViewModel { + bool get isFormValid => + this.fieldsValidationMessages.values.every((element) => element == null); + String? get emailValue => this.formValueMap[EmailValueKey] as String?; + String? get passwordValue => this.formValueMap[PasswordValueKey] as String?; + String? get shortBioValue => this.formValueMap[ShortBioValueKey] as String?; + DateTime? get birthDateValue => + this.formValueMap[BirthDateValueKey] as DateTime?; + String? get doYouLoveFoodValue => + this.formValueMap[DoYouLoveFoodValueKey] as String?; + + set emailValue(String? value) { + this.setData( + this.formValueMap + ..addAll({ + EmailValueKey: value, + }), + ); + + if (_ExampleFormViewTextEditingControllers.containsKey(EmailValueKey)) { + _ExampleFormViewTextEditingControllers[EmailValueKey]?.text = value ?? ''; + } + } + + set passwordValue(String? value) { + this.setData( + this.formValueMap + ..addAll({ + PasswordValueKey: value, + }), + ); + + if (_ExampleFormViewTextEditingControllers.containsKey(PasswordValueKey)) { + _ExampleFormViewTextEditingControllers[PasswordValueKey]?.text = + value ?? ''; + } + } + + set shortBioValue(String? value) { + this.setData( + this.formValueMap + ..addAll({ + ShortBioValueKey: value, + }), + ); + + if (_ExampleFormViewTextEditingControllers.containsKey(ShortBioValueKey)) { + _ExampleFormViewTextEditingControllers[ShortBioValueKey]?.text = + value ?? ''; + } + } + + bool get hasEmail => + this.formValueMap.containsKey(EmailValueKey) && + (emailValue?.isNotEmpty ?? false); + bool get hasPassword => + this.formValueMap.containsKey(PasswordValueKey) && + (passwordValue?.isNotEmpty ?? false); + bool get hasShortBio => + this.formValueMap.containsKey(ShortBioValueKey) && + (shortBioValue?.isNotEmpty ?? false); + bool get hasBirthDate => this.formValueMap.containsKey(BirthDateValueKey); + bool get hasDoYouLoveFood => + this.formValueMap.containsKey(DoYouLoveFoodValueKey); + + bool get hasEmailValidationMessage => + this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false; + bool get hasPasswordValidationMessage => + this.fieldsValidationMessages[PasswordValueKey]?.isNotEmpty ?? false; + bool get hasShortBioValidationMessage => + this.fieldsValidationMessages[ShortBioValueKey]?.isNotEmpty ?? false; + bool get hasBirthDateValidationMessage => + this.fieldsValidationMessages[BirthDateValueKey]?.isNotEmpty ?? false; + bool get hasDoYouLoveFoodValidationMessage => + this.fieldsValidationMessages[DoYouLoveFoodValueKey]?.isNotEmpty ?? false; + + String? get emailValidationMessage => + this.fieldsValidationMessages[EmailValueKey]; + String? get passwordValidationMessage => + this.fieldsValidationMessages[PasswordValueKey]; + String? get shortBioValidationMessage => + this.fieldsValidationMessages[ShortBioValueKey]; + String? get birthDateValidationMessage => + this.fieldsValidationMessages[BirthDateValueKey]; + String? get doYouLoveFoodValidationMessage => + this.fieldsValidationMessages[DoYouLoveFoodValueKey]; + void clearForm() { + emailValue = ''; + passwordValue = ''; + shortBioValue = ''; + } +} + +extension Methods on FormViewModel { + Future selectBirthDate( + {required BuildContext context, + required DateTime initialDate, + required DateTime firstDate, + required DateTime lastDate}) async { + final selectedDate = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate); + if (selectedDate != null) { + this.setData( + this.formValueMap..addAll({BirthDateValueKey: selectedDate})); + } + } + + void setDoYouLoveFood(String doYouLoveFood) { + this.setData( + this.formValueMap..addAll({DoYouLoveFoodValueKey: doYouLoveFood})); + } + + setEmailValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[EmailValueKey] = validationMessage; + setPasswordValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[PasswordValueKey] = validationMessage; + setShortBioValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[ShortBioValueKey] = validationMessage; + setBirthDateValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[BirthDateValueKey] = validationMessage; + setDoYouLoveFoodValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[DoYouLoveFoodValueKey] = validationMessage; +} diff --git a/packages/stacked/example/lib/ui/form/example_form_viewmodel.dart b/example/navigator_example/lib/ui/form/example_form_viewmodel.dart similarity index 57% rename from packages/stacked/example/lib/ui/form/example_form_viewmodel.dart rename to example/navigator_example/lib/ui/form/example_form_viewmodel.dart index 953a51765..e283d6302 100644 --- a/packages/stacked/example/lib/ui/form/example_form_viewmodel.dart +++ b/example/navigator_example/lib/ui/form/example_form_viewmodel.dart @@ -1,12 +1,9 @@ -import 'package:new_architecture/app/app.locator.dart'; -import 'package:new_architecture/app/app.logger.dart'; -import 'package:new_architecture/app/app.router.dart'; -import 'package:stacked_services/stacked_services.dart'; +import 'package:example/app/app.locator.dart'; +import 'package:example/app/app.logger.dart'; +import 'package:example/app/app.router.dart'; import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; -import '../../app/app.locator.dart'; -// Import the validators you want to use or define it in this class -import 'validators.dart'; import 'example_form_view.form.dart'; // #5: extend from FormViewModel @@ -18,12 +15,9 @@ class ExampleFormViewModel extends FormViewModel { void setFormStatus() { log.i('Set form Status with data: $formValueMap'); - // Set the validation message per field - setPasswordValidationMessage(passwordValidator(value: passwordValue)); - // Set a validation message for the entire form if (hasPasswordValidationMessage) { - setFormValidationMessage('Error in the form, please check again'); + setValidationMessage('Error in the form, please check again'); } } @@ -32,9 +26,12 @@ class ExampleFormViewModel extends FormViewModel { // data to the backend or db. Future? saveData() { + return null; + // here we can run custom functionality to save to our api } - Future? navigateSomewhere() => - _navigationService.navigateTo(Routes.streamCounterView); + void navigateToNewView() { + _navigationService.replaceWith(Routes.bottomNavExample); + } } diff --git a/example/navigator_example/lib/ui/form/validators.dart b/example/navigator_example/lib/ui/form/validators.dart new file mode 100644 index 000000000..5bd306a06 --- /dev/null +++ b/example/navigator_example/lib/ui/form/validators.dart @@ -0,0 +1,16 @@ +class FormValidators { + static String? passwordValidator(String? value) { + if (value == null || value.isEmpty) { + return "Password should not be empty"; + } else { + return null; + } + } + + static String? emailValidator(String? value) { + if (value == null || value.isEmpty) { + return 'Email is required'; + } + return null; + } +} diff --git a/packages/stacked/example/lib/ui/future_example_view/future_example_view.dart b/example/navigator_example/lib/ui/future_example_view/future_example_view.dart similarity index 90% rename from packages/stacked/example/lib/ui/future_example_view/future_example_view.dart rename to example/navigator_example/lib/ui/future_example_view/future_example_view.dart index 34abbd4b5..6eee7e6e7 100644 --- a/packages/stacked/example/lib/ui/future_example_view/future_example_view.dart +++ b/example/navigator_example/lib/ui/future_example_view/future_example_view.dart @@ -14,14 +14,14 @@ class FutureExampleView extends StatelessWidget { ? Container( color: Colors.red, alignment: Alignment.center, - child: Text( + child: const Text( 'An error has occered while running the future', style: TextStyle(color: Colors.white), ), ) : Center( child: viewModel.isBusy - ? CircularProgressIndicator() + ? const CircularProgressIndicator() : Text(viewModel.data!), ), ), diff --git a/packages/stacked/example/lib/ui/future_example_view/future_example_viewmodel.dart b/example/navigator_example/lib/ui/future_example_view/future_example_viewmodel.dart similarity index 100% rename from packages/stacked/example/lib/ui/future_example_view/future_example_viewmodel.dart rename to example/navigator_example/lib/ui/future_example_view/future_example_viewmodel.dart diff --git a/packages/stacked/example/lib/ui/home/home_view.dart b/example/navigator_example/lib/ui/home/home_view.dart similarity index 57% rename from packages/stacked/example/lib/ui/home/home_view.dart rename to example/navigator_example/lib/ui/home/home_view.dart index 953cfd964..9d36c333d 100644 --- a/packages/stacked/example/lib/ui/home/home_view.dart +++ b/example/navigator_example/lib/ui/home/home_view.dart @@ -1,3 +1,5 @@ +import 'package:example/datamodels/clashable_one.dart'; +import 'package:example/datamodels/home_type.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; @@ -6,31 +8,45 @@ import '../smart_widgets/widget_two/widget_two.dart'; import 'home_viewmodel.dart'; class HomeView extends StatelessWidget { + final String? title; + final bool? isLoggedIn; + final List homeTypes; + final Clashable Function(String name)? clashableGetter; + const HomeView({ Key? key, + this.title = 'hello', + this.isLoggedIn = false, + this.clashableGetter, + this.homeTypes = const [ + HomeType.apartment, + HomeType.house, + ], }) : super(key: key); @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( viewModelBuilder: () => HomeViewModel(), - onModelReady: (viewModel) => viewModel.initialise(), + onViewModelReady: (viewModel) => viewModel.initialise(), builder: (context, viewModel, child) => Scaffold( body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(viewModel.title), + if (viewModel.hasMessage) Text(viewModel.modelMessage!), + const SizedBox(height: 20), Row( mainAxisSize: MainAxisSize.min, - children: [ + children: const [ WidgetOne(), - SizedBox( - width: 50, - ), + SizedBox(width: 50), WidgetTwo(id: 2), ], ), + const SizedBox(height: 20), + if (title != null) Text(title!), ], ), ), diff --git a/packages/stacked/example/lib/ui/home/home_view_multiple_widgets.dart b/example/navigator_example/lib/ui/home/home_view_multiple_widgets.dart similarity index 72% rename from packages/stacked/example/lib/ui/home/home_view_multiple_widgets.dart rename to example/navigator_example/lib/ui/home/home_view_multiple_widgets.dart index 353e7b309..1f9b6e0df 100644 --- a/packages/stacked/example/lib/ui/home/home_view_multiple_widgets.dart +++ b/example/navigator_example/lib/ui/home/home_view_multiple_widgets.dart @@ -1,16 +1,18 @@ +import 'package:example/ui/dumb_widgets/description_section.dart'; +import 'package:example/ui/dumb_widgets/title_section.dart'; import 'package:flutter/material.dart'; -import 'package:new_architecture/ui/dumb_widgets/description_section.dart'; -import 'package:new_architecture/ui/dumb_widgets/title_section.dart'; import 'package:stacked/stacked.dart'; import 'home_viewmodel.dart'; class HomeViewMultipleWidgets extends StatelessWidget { + const HomeViewMultipleWidgets({super.key}); + @override Widget build(BuildContext context) { return ViewModelBuilder.reactive( viewModelBuilder: () => HomeViewModel(), - onModelReady: (viewModel) => viewModel.initialise(), + onViewModelReady: (viewModel) => viewModel.initialise(), builder: (context, viewModel, _) => Scaffold( floatingActionButton: FloatingActionButton( onPressed: () { @@ -19,7 +21,7 @@ class HomeViewMultipleWidgets extends StatelessWidget { ), body: Column( mainAxisAlignment: MainAxisAlignment.center, - children: [ + children: const [ TitleSection(), DescriptionSection(), ], diff --git a/packages/stacked/example/lib/ui/home/home_view_traditional.dart b/example/navigator_example/lib/ui/home/home_view_traditional.dart similarity index 86% rename from packages/stacked/example/lib/ui/home/home_view_traditional.dart rename to example/navigator_example/lib/ui/home/home_view_traditional.dart index ed2b76b0a..07953d8ab 100644 --- a/packages/stacked/example/lib/ui/home/home_view_traditional.dart +++ b/example/navigator_example/lib/ui/home/home_view_traditional.dart @@ -4,6 +4,8 @@ import 'package:stacked/stacked.dart'; import 'home_viewmodel.dart'; class HomeViewTraditional extends StatelessWidget { + const HomeViewTraditional({super.key}); + @override Widget build(BuildContext context) { // Using the withConsumer constructor gives you the traditional viewmodel @@ -11,9 +13,9 @@ class HomeViewTraditional extends StatelessWidget { // when the model does not have to be consumed by multiple different UI's. return ViewModelBuilder.reactive( viewModelBuilder: () => HomeViewModel(), - onModelReady: (viewModel) => viewModel.initialise(), + onViewModelReady: (viewModel) => viewModel.initialise(), builder: (context, viewModel, child) => Scaffold( - floatingActionButton: UpdateTitleButton(), + floatingActionButton: const UpdateTitleButton(), body: Center( child: Text(viewModel.title), ), diff --git a/packages/stacked/example/lib/ui/home/home_viewmodel.dart b/example/navigator_example/lib/ui/home/home_viewmodel.dart similarity index 54% rename from packages/stacked/example/lib/ui/home/home_viewmodel.dart rename to example/navigator_example/lib/ui/home/home_viewmodel.dart index a635f11a6..7492150a0 100644 --- a/packages/stacked/example/lib/ui/home/home_viewmodel.dart +++ b/example/navigator_example/lib/ui/home/home_viewmodel.dart @@ -1,30 +1,28 @@ -import 'package:new_architecture/app/app.locator.dart'; -import 'package:new_architecture/app/app.logger.dart'; -import 'package:new_architecture/app/app.router.dart'; +import 'package:example/app/app.locator.dart'; +import 'package:example/app/app.logger.dart'; +import 'package:example/app/app.router.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; -class HomeViewModel extends BaseViewModel { +class HomeViewModel extends BaseViewModel with MessageStateHelper { final log = getLogger('HomeViewModel'); - final NavigationService _navigationService = exampleLocator(); - - HomeViewModel() { - log.d('created'); - } + final NavigationService _navigationService = + exampleLocator(); String title = 'default'; + int counter = 0; void navigate() { - _navigationService.navigateTo(Routes.nonReactiveView); + _navigationService.navigateToNonReactiveView(); } void initialise() { + setMessage('initialise'); log.i('initialise'); - title = 'initialised'; + title = 'Initialised'; notifyListeners(); } - int counter = 0; void updateTitle() { counter++; title = '$counter'; diff --git a/packages/stacked/example/lib/ui/multiple_futures_example/multiple_futures_example_view.dart b/example/navigator_example/lib/ui/multiple_futures_example/multiple_futures_example_view.dart similarity index 84% rename from packages/stacked/example/lib/ui/multiple_futures_example/multiple_futures_example_view.dart rename to example/navigator_example/lib/ui/multiple_futures_example/multiple_futures_example_view.dart index 00e8fbf64..bc4cb10b6 100644 --- a/packages/stacked/example/lib/ui/multiple_futures_example/multiple_futures_example_view.dart +++ b/example/navigator_example/lib/ui/multiple_futures_example/multiple_futures_example_view.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:new_architecture/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart'; +import 'package:example/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart'; import 'package:stacked/stacked.dart'; class MultipleFuturesExampleView extends StatelessWidget { @@ -19,10 +19,10 @@ class MultipleFuturesExampleView extends StatelessWidget { alignment: Alignment.center, color: Colors.yellow, child: viewModel.fetchingNumber - ? CircularProgressIndicator() + ? const CircularProgressIndicator() : Text(viewModel.fetchedNumber.toString()), ), - SizedBox( + const SizedBox( width: 20, ), Container( @@ -31,7 +31,7 @@ class MultipleFuturesExampleView extends StatelessWidget { alignment: Alignment.center, color: Colors.red, child: viewModel.fetchingString - ? CircularProgressIndicator() + ? const CircularProgressIndicator() : Text(viewModel.fetchedString), ), ], diff --git a/example/navigator_example/lib/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart b/example/navigator_example/lib/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart new file mode 100644 index 000000000..5a9b47613 --- /dev/null +++ b/example/navigator_example/lib/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart @@ -0,0 +1,28 @@ +import 'package:stacked/stacked.dart'; + +const String _numberDelayFuture = 'delayedNumber'; +const String _stringDelayFuture = 'delayedString'; + +class MultipleFuturesExampleViewModel extends MultipleFutureViewModel { + int get fetchedNumber => dataMap![_numberDelayFuture]; + String get fetchedString => dataMap![_stringDelayFuture]; + + bool get fetchingNumber => busy(_numberDelayFuture); + bool get fetchingString => busy(_stringDelayFuture); + + @override + Map get futuresMap => { + _numberDelayFuture: getNumberAfterDelay, + _stringDelayFuture: getStringAfterDelay, + }; + + Future getNumberAfterDelay() async { + await Future.delayed(const Duration(seconds: 2)); + return 3; + } + + Future getStringAfterDelay() async { + await Future.delayed(const Duration(seconds: 3)); + return 'String data'; + } +} diff --git a/packages/stacked/example/lib/ui/multiple_streams_example/multiple_streams_example_view.dart b/example/navigator_example/lib/ui/multiple_streams_example/multiple_streams_example_view.dart similarity index 82% rename from packages/stacked/example/lib/ui/multiple_streams_example/multiple_streams_example_view.dart rename to example/navigator_example/lib/ui/multiple_streams_example/multiple_streams_example_view.dart index fffe4248c..fdd7f9d3f 100644 --- a/packages/stacked/example/lib/ui/multiple_streams_example/multiple_streams_example_view.dart +++ b/example/navigator_example/lib/ui/multiple_streams_example/multiple_streams_example_view.dart @@ -20,15 +20,15 @@ class MultipleStreamsExampleView extends StatelessWidget { alignment: Alignment.center, color: Colors.yellow, child: !viewModel.hasNumberData - ? CircularProgressIndicator() + ? const CircularProgressIndicator() : Column( children: [ - Text(viewModel.stringStreamDelay.toString() + 'ms'), + Text('${viewModel.stringStreamDelay}ms'), Text(viewModel.number.toString()), ], ), ), - SizedBox( + const SizedBox( width: 20, ), Container( @@ -37,11 +37,10 @@ class MultipleStreamsExampleView extends StatelessWidget { alignment: Alignment.center, color: Colors.red, child: !viewModel.hasRandomString - ? CircularProgressIndicator() + ? const CircularProgressIndicator() : Column( children: [ - Text( - viewModel.numbersStreamDelay.toString() + 'ms'), + Text('${viewModel.numbersStreamDelay}ms'), Text(viewModel.randomString), ], ), @@ -50,8 +49,8 @@ class MultipleStreamsExampleView extends StatelessWidget { ), ), floatingActionButton: MaterialButton( - child: Text('Change Stream Source Faster'), onPressed: viewModel.swapStreams, + child: const Text('Change Stream Source Faster'), ), ), viewModelBuilder: () => MultipleStreamsExampleViewModel()); diff --git a/packages/stacked/example/lib/ui/multiple_streams_example/multiple_streams_example_viewmodel.dart b/example/navigator_example/lib/ui/multiple_streams_example/multiple_streams_example_viewmodel.dart similarity index 69% rename from packages/stacked/example/lib/ui/multiple_streams_example/multiple_streams_example_viewmodel.dart rename to example/navigator_example/lib/ui/multiple_streams_example/multiple_streams_example_viewmodel.dart index 41eddd11e..a9eee4fca 100644 --- a/packages/stacked/example/lib/ui/multiple_streams_example/multiple_streams_example_viewmodel.dart +++ b/example/navigator_example/lib/ui/multiple_streams_example/multiple_streams_example_viewmodel.dart @@ -1,15 +1,16 @@ import 'dart:math'; + import 'package:stacked/stacked.dart'; -const String _NumbersStreamKey = 'numbers-stream'; -const String _StringStreamKey = 'string-stream'; +const String _numbersStreamKey = 'numbers-stream'; +const String _stringStreamKey = 'string-stream'; class MultipleStreamsExampleViewModel extends MultipleStreamViewModel { - int get number => dataMap![_NumbersStreamKey]; - bool get hasNumberData => dataReady(_NumbersStreamKey); + int get number => dataMap![_numbersStreamKey]; + bool get hasNumberData => dataReady(_numbersStreamKey); - String get randomString => dataMap![_StringStreamKey]; - bool get hasRandomString => dataReady(_StringStreamKey); + String get randomString => dataMap![_stringStreamKey]; + bool get hasRandomString => dataReady(_stringStreamKey); Stream numbersStream([int delay = 500]) async* { var random = Random(); @@ -37,8 +38,8 @@ class MultipleStreamsExampleViewModel extends MultipleStreamViewModel { @override Map get streamsMap => { - _NumbersStreamKey: StreamData(numbersStream(numbersStreamDelay)), - _StringStreamKey: StreamData(stringStream(stringStreamDelay)), + _numbersStreamKey: StreamData(numbersStream(numbersStreamDelay)), + _stringStreamKey: StreamData(stringStream(stringStreamDelay)), }; void swapStreams() { diff --git a/example/navigator_example/lib/ui/nonreactive/nonreactive_view.dart b/example/navigator_example/lib/ui/nonreactive/nonreactive_view.dart new file mode 100644 index 000000000..4298913b0 --- /dev/null +++ b/example/navigator_example/lib/ui/nonreactive/nonreactive_view.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'nonreactive_viewmodel.dart'; + +class NonReactiveView extends StatelessWidget { + const NonReactiveView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.nonReactive( + builder: (context, viewModel, child) => Scaffold( + appBar: AppBar( + title: const Text('Non Reactive View'), + centerTitle: true, + leading: IconButton( + onPressed: () { + viewModel.navigateBackHome(); + }, + icon: const Icon(Icons.arrow_back_ios)), + ), + floatingActionButton: FloatingActionButton( + onPressed: viewModel.updateTitle, + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: viewModel.navigateToNewView, + child: const Text('Go to stream counter view'), + ), + const SizedBox(height: 10), + Text(viewModel.title), + ], + ), + ), + ), + viewModelBuilder: () => NonReactiveViewModel(), + ); + } +} diff --git a/example/navigator_example/lib/ui/nonreactive/nonreactive_viewmodel.dart b/example/navigator_example/lib/ui/nonreactive/nonreactive_viewmodel.dart new file mode 100644 index 000000000..8892bc6ef --- /dev/null +++ b/example/navigator_example/lib/ui/nonreactive/nonreactive_viewmodel.dart @@ -0,0 +1,28 @@ +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; + +import '../../app/app.locator.dart'; +import '../../app/app.router.dart'; +import '../../datamodels/clashable_two.dart'; + +class NonReactiveViewModel extends BaseViewModel { + final _navigationService = exampleLocator(); + String title = 'This should not change'; + + void updateTitle() { + title += '. This has changed'; + notifyListeners(); + } + + void navigateToNewView() { + _navigationService + .navigateToStreamCounterView(clashableTwo: [const Clashable(22)]); + } + + void navigateBackHome() { + _navigationService.clearStackAndShow( + Routes.homeView, + arguments: const HomeViewArguments(title: 'Home'), + ); + } +} diff --git a/packages/stacked/example/lib/ui/smart_widgets/widget_one/widget_one.dart b/example/navigator_example/lib/ui/smart_widgets/widget_one/widget_one.dart similarity index 92% rename from packages/stacked/example/lib/ui/smart_widgets/widget_one/widget_one.dart rename to example/navigator_example/lib/ui/smart_widgets/widget_one/widget_one.dart index 9c81c5942..bd73b96ab 100644 --- a/packages/stacked/example/lib/ui/smart_widgets/widget_one/widget_one.dart +++ b/example/navigator_example/lib/ui/smart_widgets/widget_one/widget_one.dart @@ -21,20 +21,20 @@ class WidgetOne extends StatelessWidget { ? Column( mainAxisSize: MainAxisSize.min, children: [ - Text( + const Text( 'Tap to increment', style: TextStyle(fontSize: 10), ), Text( viewModel.postCount.toString(), - style: TextStyle( + style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 40, ), ), ], ) - : Center( + : const Center( child: CircularProgressIndicator(), ), ), diff --git a/packages/stacked/example/lib/ui/smart_widgets/widget_one/widget_one_viewmodel.dart b/example/navigator_example/lib/ui/smart_widgets/widget_one/widget_one_viewmodel.dart similarity index 64% rename from packages/stacked/example/lib/ui/smart_widgets/widget_one/widget_one_viewmodel.dart rename to example/navigator_example/lib/ui/smart_widgets/widget_one/widget_one_viewmodel.dart index 257222cd8..eae4c942a 100644 --- a/packages/stacked/example/lib/ui/smart_widgets/widget_one/widget_one_viewmodel.dart +++ b/example/navigator_example/lib/ui/smart_widgets/widget_one/widget_one_viewmodel.dart @@ -1,5 +1,5 @@ -import 'package:new_architecture/app/app.locator.dart'; -import 'package:new_architecture/services/information_service.dart'; +import 'package:example/app/app.locator.dart'; +import 'package:example/services/information_service.dart'; import 'package:stacked/stacked.dart'; class WidgetOneViewModel extends ReactiveViewModel { @@ -12,7 +12,7 @@ class WidgetOneViewModel extends ReactiveViewModel { } Future longUpdateStuff() async { - var result = await runBusyFuture(updateStuff()); + await runBusyFuture(updateStuff()); } Future updateStuff() { @@ -20,5 +20,5 @@ class WidgetOneViewModel extends ReactiveViewModel { } @override - List get reactiveServices => [_informationService]; + List get listenableServices => [_informationService]; } diff --git a/packages/stacked/example/lib/ui/smart_widgets/widget_two/widget_two.dart b/example/navigator_example/lib/ui/smart_widgets/widget_two/widget_two.dart similarity index 94% rename from packages/stacked/example/lib/ui/smart_widgets/widget_two/widget_two.dart rename to example/navigator_example/lib/ui/smart_widgets/widget_two/widget_two.dart index 3ce4f339d..bf72ebcd5 100644 --- a/packages/stacked/example/lib/ui/smart_widgets/widget_two/widget_two.dart +++ b/example/navigator_example/lib/ui/smart_widgets/widget_two/widget_two.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; + import 'widget_two_viewmodel.dart'; class WidgetTwo extends StatelessWidget { @@ -23,13 +24,13 @@ class WidgetTwo extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text( + const Text( 'Tap to Reset', style: TextStyle(fontSize: 10), ), Text( viewModel.postCount.toString(), - style: TextStyle( + style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 40, ), diff --git a/packages/stacked/example/lib/ui/smart_widgets/widget_two/widget_two_viewmodel.dart b/example/navigator_example/lib/ui/smart_widgets/widget_two/widget_two_viewmodel.dart similarity index 62% rename from packages/stacked/example/lib/ui/smart_widgets/widget_two/widget_two_viewmodel.dart rename to example/navigator_example/lib/ui/smart_widgets/widget_two/widget_two_viewmodel.dart index dd7ffae5a..cfe489404 100644 --- a/packages/stacked/example/lib/ui/smart_widgets/widget_two/widget_two_viewmodel.dart +++ b/example/navigator_example/lib/ui/smart_widgets/widget_two/widget_two_viewmodel.dart @@ -1,5 +1,5 @@ -import 'package:new_architecture/app/app.locator.dart'; -import 'package:new_architecture/services/information_service.dart'; +import 'package:example/app/app.locator.dart'; +import 'package:example/services/information_service.dart'; import 'package:stacked/stacked.dart'; class WidgetTwoViewModel extends ReactiveViewModel { @@ -14,5 +14,5 @@ class WidgetTwoViewModel extends ReactiveViewModel { } @override - List get reactiveServices => [_informationService]; + List get listenableServices => [_informationService]; } diff --git a/packages/stacked/example/lib/ui/startup/startup_view.dart b/example/navigator_example/lib/ui/startup/startup_view.dart similarity index 77% rename from packages/stacked/example/lib/ui/startup/startup_view.dart rename to example/navigator_example/lib/ui/startup/startup_view.dart index c3297cee6..017769b8c 100644 --- a/packages/stacked/example/lib/ui/startup/startup_view.dart +++ b/example/navigator_example/lib/ui/startup/startup_view.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:new_architecture/ui/startup/startup_viewmodel.dart'; +import 'package:example/ui/startup/startup_viewmodel.dart'; import 'package:stacked/stacked.dart'; class StartupView extends StatelessWidget { @@ -9,7 +9,7 @@ class StartupView extends StatelessWidget { Widget build(BuildContext context) { return ViewModelBuilder.reactive( viewModelBuilder: () => StartupVieWModel(), - builder: (context, viewModel, child) => Scaffold( + builder: (context, viewModel, child) => const Scaffold( body: Center( child: Text('Startup View'), ), diff --git a/packages/stacked/example/lib/ui/startup/startup_viewmodel.dart b/example/navigator_example/lib/ui/startup/startup_viewmodel.dart similarity index 100% rename from packages/stacked/example/lib/ui/startup/startup_viewmodel.dart rename to example/navigator_example/lib/ui/startup/startup_viewmodel.dart diff --git a/example/navigator_example/lib/ui/stream_view/stream_counter_view.dart b/example/navigator_example/lib/ui/stream_view/stream_counter_view.dart new file mode 100644 index 000000000..22d9cc389 --- /dev/null +++ b/example/navigator_example/lib/ui/stream_view/stream_counter_view.dart @@ -0,0 +1,47 @@ +import 'package:example/datamodels/clashable_two.dart'; +import 'package:flutter/material.dart'; +import 'package:example/ui/stream_view/stream_counter_viewmodel.dart'; +import 'package:stacked/stacked.dart'; + +class StreamCounterView extends StatelessWidget { + final List clashableTwo; + const StreamCounterView({Key? key, required this.clashableTwo}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + builder: (context, viewModel, child) => Scaffold( + appBar: AppBar( + title: const Text('Stream Counter View'), + centerTitle: true, + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: viewModel.changeStreamSources, + child: const Text('Change Stream Sources'), + ), + const SizedBox(height: 10), + Text( + viewModel.title, + textAlign: TextAlign.center, + ), + const SizedBox(height: 20), + Text( + viewModel.streamSource, + textAlign: TextAlign.center, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: viewModel.navigateToNewView, + ), + ), + viewModelBuilder: () => StreamCounterViewModel(), + ); + } +} diff --git a/packages/stacked/example/lib/ui/stream_view/stream_counter_viewmodel.dart b/example/navigator_example/lib/ui/stream_view/stream_counter_viewmodel.dart similarity index 58% rename from packages/stacked/example/lib/ui/stream_view/stream_counter_viewmodel.dart rename to example/navigator_example/lib/ui/stream_view/stream_counter_viewmodel.dart index d1aa2b935..0e091bcdb 100644 --- a/packages/stacked/example/lib/ui/stream_view/stream_counter_viewmodel.dart +++ b/example/navigator_example/lib/ui/stream_view/stream_counter_viewmodel.dart @@ -1,13 +1,20 @@ -import 'package:new_architecture/app/app.locator.dart'; -import 'package:new_architecture/services/epoch_service.dart'; +import 'package:example/app/app.locator.dart'; +import 'package:example/datamodels/clashable_one.dart'; +import 'package:example/services/epoch_service.dart'; +import 'package:example/ui/form/example_form_view.dart'; import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; class StreamCounterViewModel extends StreamViewModel { + final _navigationService = exampleLocator(); + String get title => 'This is the time since epoch in seconds \n $data'; late Stream _currentSource; bool isSlowEpochNumbers = true; + String get streamSource => isSlowEpochNumbers ? 'Slow' : 'Fast'; + StreamCounterViewModel() { _setSource(); } @@ -38,4 +45,10 @@ class StreamCounterViewModel extends StreamViewModel { _setSource(); notifySourceChanged(); } + + void navigateToNewView() { + _navigationService.navigateWithTransition( + ExampleFormView(clashableOne: const Clashable('one')), + transitionStyle: Transition.zoom); + } } diff --git a/packages/stacked/example/pubspec.yaml b/example/navigator_example/pubspec.yaml similarity index 84% rename from packages/stacked/example/pubspec.yaml rename to example/navigator_example/pubspec.yaml index 7e3725ecc..8c89cd7a6 100644 --- a/packages/stacked/example/pubspec.yaml +++ b/example/navigator_example/pubspec.yaml @@ -1,4 +1,4 @@ -name: new_architecture +name: example description: A new Flutter project. publish_to: none @@ -15,7 +15,7 @@ publish_to: none version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.5 <3.0.0" dependencies: flutter: @@ -23,35 +23,50 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.2 + cupertino_icons: ^1.0.2 # state management stacked: - # path: ../ stacked_services: - # path: ../../stacked_services/ stacked_hooks: - flutter_hooks: stacked_themes: - ^0.3.2 - # path: ../../stacked_themes/ stacked_crashlytics: - # path: ../../stacked_crashlytics/ + stacked_shared: + + flutter_hooks: ^0.18.4 # navigation - get: + get: ^4.6.3 + animations: ^2.0.3 - # logging - logger: +dependency_overrides: + stacked: + path: ../../ + stacked_services: + path: ../../../services/ + stacked_hooks: + path: ../../../hooks/ + stacked_themes: + path: ../../../themes/ + stacked_crashlytics: + path: ../../../crashlytics/ + stacked_generator: + path: ../../../generator/ + stacked_shared: + path: ../../../core/ dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter # dependency injection build_runner: stacked_generator: # path: ../../stacked_generator + flutter_lints: + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/example/navigator_example/test/stacked_example_test.dart b/example/navigator_example/test/stacked_example_test.dart new file mode 100644 index 000000000..978d00e35 --- /dev/null +++ b/example/navigator_example/test/stacked_example_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('StackedExampleTest -', () { + group('test -', () { + test('just a place holder', () { + expect(true, isTrue); + }); + }); + }); +} diff --git a/example/navigator_example/test_driver/integration_test.dart b/example/navigator_example/test_driver/integration_test.dart new file mode 100644 index 000000000..b38629cca --- /dev/null +++ b/example/navigator_example/test_driver/integration_test.dart @@ -0,0 +1,3 @@ +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/packages/stacked_localisation/stacked_localisation/example/.gitignore b/example/router_example/.gitignore similarity index 77% rename from packages/stacked_localisation/stacked_localisation/example/.gitignore rename to example/router_example/.gitignore index 1ba9c339e..24476c5d1 100644 --- a/packages/stacked_localisation/stacked_localisation/example/.gitignore +++ b/example/router_example/.gitignore @@ -8,6 +8,7 @@ .buildlog/ .history .svn/ +migrate_working_dir/ # IntelliJ related *.iml @@ -22,6 +23,7 @@ # Flutter/Dart/Pub related **/doc/api/ +**/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies @@ -30,14 +32,13 @@ .pub/ /build/ -# Web related -lib/generated_plugin_registrant.dart - # Symbolication related app.*.symbols # Obfuscation related app.*.map.json -# Exceptions to above rules. -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/example/router_example/.metadata b/example/router_example/.metadata new file mode 100644 index 000000000..0cffca3b7 --- /dev/null +++ b/example/router_example/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + - platform: android + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + - platform: ios + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + - platform: linux + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + - platform: macos + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + - platform: web + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + - platform: windows + create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/stacked_hooks/example/README.md b/example/router_example/README.md similarity index 96% rename from packages/stacked_hooks/example/README.md rename to example/router_example/README.md index a13562602..7b576271c 100644 --- a/packages/stacked_hooks/example/README.md +++ b/example/router_example/README.md @@ -1,4 +1,4 @@ -# example +# new_architecture A new Flutter project. diff --git a/example/router_example/analysis_options.yaml b/example/router_example/analysis_options.yaml new file mode 100644 index 000000000..61b6c4de1 --- /dev/null +++ b/example/router_example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/stacked_hooks/example/android/.gitignore b/example/router_example/android/.gitignore similarity index 100% rename from packages/stacked_hooks/example/android/.gitignore rename to example/router_example/android/.gitignore diff --git a/packages/stacked_hooks/example/android/app/build.gradle b/example/router_example/android/app/build.gradle similarity index 95% rename from packages/stacked_hooks/example/android/app/build.gradle rename to example/router_example/android/app/build.gradle index 0f6a5e54b..587620b3d 100644 --- a/packages/stacked_hooks/example/android/app/build.gradle +++ b/example/router_example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 28 + compileSdkVersion 31 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -38,7 +38,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.example" + applicationId "com.example.new_architecture" minSdkVersion 16 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() @@ -63,5 +63,5 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } diff --git a/packages/stacked_localisation/stacked_localisation/example/android/app/src/debug/AndroidManifest.xml b/example/router_example/android/app/src/debug/AndroidManifest.xml similarity index 86% rename from packages/stacked_localisation/stacked_localisation/example/android/app/src/debug/AndroidManifest.xml rename to example/router_example/android/app/src/debug/AndroidManifest.xml index c208884f3..dcb365cd6 100644 --- a/packages/stacked_localisation/stacked_localisation/example/android/app/src/debug/AndroidManifest.xml +++ b/example/router_example/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.example.new_architecture"> diff --git a/packages/stacked_hooks/example/android/app/src/main/AndroidManifest.xml b/example/router_example/android/app/src/main/AndroidManifest.xml similarity index 54% rename from packages/stacked_hooks/example/android/app/src/main/AndroidManifest.xml rename to example/router_example/android/app/src/main/AndroidManifest.xml index 8bc6007d8..09fe11a9d 100644 --- a/packages/stacked_hooks/example/android/app/src/main/AndroidManifest.xml +++ b/example/router_example/android/app/src/main/AndroidManifest.xml @@ -1,21 +1,11 @@ - + - - + + @@ -23,8 +13,6 @@ - + diff --git a/example/router_example/android/app/src/main/kotlin/com/example/new_architecture/MainActivity.kt b/example/router_example/android/app/src/main/kotlin/com/example/new_architecture/MainActivity.kt new file mode 100644 index 000000000..be03abb41 --- /dev/null +++ b/example/router_example/android/app/src/main/kotlin/com/example/new_architecture/MainActivity.kt @@ -0,0 +1,7 @@ +package com.example.new_architecture + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { + +} diff --git a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/example/router_example/android/app/src/main/kotlin/com/example/router_example/MainActivity.kt similarity index 73% rename from packages/stacked_localisation/stacked_localisation/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt rename to example/router_example/android/app/src/main/kotlin/com/example/router_example/MainActivity.kt index e793a000d..cd71af07e 100644 --- a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ b/example/router_example/android/app/src/main/kotlin/com/example/router_example/MainActivity.kt @@ -1,4 +1,4 @@ -package com.example.example +package com.example.router_example import io.flutter.embedding.android.FlutterActivity diff --git a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/drawable/launch_background.xml b/example/router_example/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 86% rename from packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/drawable/launch_background.xml rename to example/router_example/android/app/src/main/res/drawable-v21/launch_background.xml index 304732f88..f74085f3f 100644 --- a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/drawable/launch_background.xml +++ b/example/router_example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -1,7 +1,7 @@ - + + diff --git a/packages/stacked_hooks/example/android/app/src/main/res/values/styles.xml b/example/router_example/android/app/src/main/res/values/styles.xml similarity index 100% rename from packages/stacked_hooks/example/android/app/src/main/res/values/styles.xml rename to example/router_example/android/app/src/main/res/values/styles.xml diff --git a/packages/stacked_hooks/example/android/app/src/debug/AndroidManifest.xml b/example/router_example/android/app/src/profile/AndroidManifest.xml similarity index 86% rename from packages/stacked_hooks/example/android/app/src/debug/AndroidManifest.xml rename to example/router_example/android/app/src/profile/AndroidManifest.xml index c208884f3..dcb365cd6 100644 --- a/packages/stacked_hooks/example/android/app/src/debug/AndroidManifest.xml +++ b/example/router_example/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.example.new_architecture"> diff --git a/packages/stacked_localisation/stacked_localisation/example/android/build.gradle b/example/router_example/android/build.gradle similarity index 94% rename from packages/stacked_localisation/stacked_localisation/example/android/build.gradle rename to example/router_example/android/build.gradle index 3100ad2d5..7087ad19f 100644 --- a/packages/stacked_localisation/stacked_localisation/example/android/build.gradle +++ b/example/router_example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.4.10' repositories { google() jcenter() diff --git a/packages/stacked_hooks/example/android/gradle.properties b/example/router_example/android/gradle.properties similarity index 100% rename from packages/stacked_hooks/example/android/gradle.properties rename to example/router_example/android/gradle.properties diff --git a/packages/stacked_hooks/example/android/gradle/wrapper/gradle-wrapper.properties b/example/router_example/android/gradle/wrapper/gradle-wrapper.properties similarity index 93% rename from packages/stacked_hooks/example/android/gradle/wrapper/gradle-wrapper.properties rename to example/router_example/android/gradle/wrapper/gradle-wrapper.properties index 296b146b7..bc24dcf03 100644 --- a/packages/stacked_hooks/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/router_example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/packages/stacked_hooks/example/android/settings.gradle b/example/router_example/android/settings.gradle similarity index 100% rename from packages/stacked_hooks/example/android/settings.gradle rename to example/router_example/android/settings.gradle diff --git a/example/router_example/build.yaml b/example/router_example/build.yaml new file mode 100644 index 000000000..417357441 --- /dev/null +++ b/example/router_example/build.yaml @@ -0,0 +1,6 @@ +targets: + $default: + builders: + stacked_generator|stackedRouterGenerator: + options: + navigator2: true \ No newline at end of file diff --git a/example/router_example/integration_test/app_test.dart b/example/router_example/integration_test/app_test.dart new file mode 100644 index 000000000..561ec54aa --- /dev/null +++ b/example/router_example/integration_test/app_test.dart @@ -0,0 +1,306 @@ +import 'package:example/main.dart' as app; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('Integration Test', () { + testWidgets('Ensure all stacked integrations still work as expected', + (WidgetTester tester) async { + // Build our app and trigger a frame. + app.main(); + await tester.pumpAndSettle(); + + // Arrange + + // Home View Widgets + final initialisedText = find.text('Initialised'); + final widgetOne = find.text('Tap to increment'); + final widgetTwo = find.text('Tap to Reset'); + final widgetThree = find.text('Home'); + + // Non Reactive View Widgets + final nonReactiveTitle = find.text('Non Reactive View'); + final goToStreamCounterButton = find.text('Go to stream counter view'); + final title = find.text('This should not change'); + + // Stream Counter View Widgets + final streamCounterTitle = find.text('Stream Counter View'); + final streamSourceButton = find.text('Change Stream Sources'); + final streamSlowSourceText = find.text('Slow'); + final streamFastSourceText = find.text('Fast'); + + // Example Form View Widgets + final exampleFormTitle = find.text('Example Form View'); + final formLoremText = find.text('Lorem'); + final formPasswordTextField = find.byKey(const ValueKey('passwordField')); + final passwordErrorText = find.text('Password should not be empty'); + final dobButton = find.text('Select your Date of birth'); + final date = DateTime(DateTime.now().year, DateTime.now().month, 11); + final dateString = date.toString(); + final dropDownButton = find.byKey(const ValueKey('dropdownField')); + final dropDownItem = find.text('No').last; + final dropDownButtonNo = find.text('No'); + + // Bottom Navigation Bar Widgets + final bottomNavigationBar = find.byType(BottomNavigationBar); + final bottomNavigationBarItemOne = find.text('Favorites'); + final bottomNavigationBarItemTwo = find.text('History'); + final bottomNavigationBarItemThree = find.text('Profile'); + final profileView = find.text('Profile'); + + // General Widget + final fab = find.byType(FloatingActionButton); + final iconButton = find.byType(IconButton); + + // Home View Test + tester.printToConsole('Home view Testing'); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + + expect(initialisedText, findsOneWidget); + expect(widgetOne, findsOneWidget); + expect(widgetTwo, findsOneWidget); + expect(fab, findsOneWidget); + expect(widgetThree, findsNothing); + + // Emulate a tap on the widget one button 3 times. + tester.printToConsole('Emulating tap on widget one button 3 times'); + await tester.tap(widgetOne); + await tester.tap(widgetOne); + await tester.tap(widgetOne); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the widget one and two text has been updated to 3. + tester.printToConsole( + 'Verifying widget one and two text has been updated to 3'); + expect(find.text('3'), findsWidgets); + + // Emulate a tap on the widget two button to reset the counter. + tester.printToConsole('Emulating tap on widget two button'); + await tester.tap(widgetTwo); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the widget one and two text has been updated to 0. + tester.printToConsole( + 'Verifying widget one and two text has been updated to 0'); + expect(find.text('0'), findsWidgets); + + // Emulate a tap on the fab button to navigate to the second view. + tester.printToConsole( + 'Emulating tap on fab button to navigate to the second view'); + await tester.tap(fab); + // Trigger a frame. + await tester.pumpAndSettle(); + + /// Emulating a tap on the back icon button. + await tester.tap(iconButton); + // Trigger a frame. + await tester.pumpAndSettle(); + expect(widgetThree, findsOneWidget); + + await tester.tap(fab); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Non Reactive View Test + tester.printToConsole('Non Reactive view Testing'); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + expect(nonReactiveTitle, findsOneWidget); + expect(goToStreamCounterButton, findsOneWidget); + expect(title, findsOneWidget); + expect(fab, findsOneWidget); + + // Emulate a tap on FAB button + tester.printToConsole('Emulating tap on FAB button'); + await tester.tap(fab); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the title didn't update. + tester.printToConsole('Verify the title didn\'t update'); + expect(title, findsOneWidget); + + // Emulate a tap on the go to stream counter button. + tester.printToConsole('Emulating tap on the go to stream counter button'); + await tester.tap(goToStreamCounterButton); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Stream Counter View Test + tester.printToConsole('Stream Counter view Testing'); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + expect(streamCounterTitle, findsOneWidget); + expect(streamSourceButton, findsOneWidget); + expect(streamSlowSourceText, findsOneWidget); + expect(fab, findsOneWidget); + + // Emulate a tap on the stream source button. + tester.printToConsole('Emulating tap on the stream source button'); + await tester.tap(streamSourceButton); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify it has changed to the fast source. + tester.printToConsole('Verify it has changed to the fast source'); + expect(streamFastSourceText, findsOneWidget); + expect(streamSlowSourceText, findsNothing); + + // Emulate a tap on the back button. + tester.printToConsole('Emulating tap on the back button'); + await tester.pageBack(); + // Trigger a frame. + await tester.pumpAndSettle(); + tester.printToConsole('Navigating back to non reactive view'); + + // Verify the widgets are not present. + tester.printToConsole('Verify the widgets are not present'); + expect(streamCounterTitle, findsNothing); + expect(streamSourceButton, findsNothing); + + // Emulate a tap on the Go to stream counter button. + tester.printToConsole('Emulating tap on the Go to stream counter button'); + await tester.tap(goToStreamCounterButton); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Emulate a tap on the FAB button. + tester.printToConsole('Emulating tap on the FAB button'); + await tester.tap(fab); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Example Form View Test + tester.printToConsole('Example Form View Testing'); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + + expect(exampleFormTitle, findsOneWidget); + expect(formLoremText, findsOneWidget); + expect(formPasswordTextField, findsOneWidget); + expect(dobButton, findsOneWidget); + expect(dropDownButton, findsOneWidget); + + // Emulate a tap on the dob button. + tester.printToConsole('Emulating tap on the dob button'); + await tester.tap(dobButton); + // Trigger a frame. + await tester.pumpAndSettle(); + await tester.tap(find.text('11')); + await tester.tap(find.text('OK')); + // Trigger a frame. + await tester.pump(); + + // Verify the dob button has been updated. + tester.printToConsole('Verify the dob button has been updated'); + expect(find.text(dateString), findsOneWidget); + + // Emulate a tap on the drop down button. + tester.printToConsole('Emulating tap on the drop down button'); + await tester.tap(dropDownButton); + // Trigger a frame. + await tester.pumpAndSettle(); + await tester.tap(dropDownItem); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the drop down button has been updated. + tester.printToConsole('Verify the drop down button has been updated'); + expect(dropDownButtonNo, findsOneWidget); + + // Verify the password text field has been updated to empty string. + tester.printToConsole('Verify the password text field has empty string'); + + expect(find.text('password123'), findsNothing); + //verify error for password is shown when form is submitted and password is empty. + await tester.tap(fab); + await tester.pumpAndSettle(); + expect(passwordErrorText, findsOneWidget); + + // Emulate entering text into the password text field. + tester.printToConsole( + 'Emulating entering text into the password text field'); + await tester.enterText(formPasswordTextField, 'password123'); + + // Emulate navigating to bottom navigation bar view. + tester + .printToConsole('Emulating navigating to bottom navigation bar view'); + await tester.tap(fab); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + expect(bottomNavigationBar, findsOneWidget); + expect(bottomNavigationBarItemOne, findsOneWidget); + expect(bottomNavigationBarItemTwo, findsOneWidget); + expect(bottomNavigationBarItemThree, findsOneWidget); + + // Emulate a tap on the bottom navigation bar item one. + tester.printToConsole( + 'Emulating tap on the bottom navigation bar item one'); + await tester.tap(bottomNavigationBarItemOne); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + expect(find.text('999'), findsOneWidget); + + // Increase the counter. + tester.printToConsole('Increasing the counter'); + await tester.tap(fab); + await tester.tap(fab); + await tester.tap(fab); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the counter has been increased. + tester.printToConsole('Verify the counter has been increased'); + + expect(find.text('1002'), findsOneWidget); + + // Emulate a tap on the bottom navigation bar item two. + tester.printToConsole( + 'Emulating tap on the bottom navigation bar item two'); + await tester.tap(bottomNavigationBarItemTwo); + // Trigger a frame. + await tester.pump(const Duration(milliseconds: 100)); + + // Verify circular progress indicator is present. + tester.printToConsole('Verify circular progress indicator is present'); + expect(find.byType(CircularProgressIndicator), findsOneWidget); + + // Verify after 2 seconds the circular progress indicator is not present. + tester.printToConsole( + 'Verify after 2 seconds the circular progress indicator is not present'); + await tester.pumpAndSettle(const Duration(seconds: 2)); + expect(find.text('100'), findsOneWidget); + + // Emulate a tap on the bottom navigation bar item three. + tester.printToConsole( + 'Emulating tap on the bottom navigation bar item three'); + + await tester.tap(bottomNavigationBarItemThree); + // Trigger a frame. + await tester.pumpAndSettle(); + + // Verify the widgets are present. + tester.printToConsole('Verify the widgets are present'); + + expect(profileView, findsWidgets); + + tester.printToConsole('Test completed successfully πŸŽ‰'); + }); + }); +} diff --git a/packages/stacked_hooks/example/ios/.gitignore b/example/router_example/ios/.gitignore similarity index 100% rename from packages/stacked_hooks/example/ios/.gitignore rename to example/router_example/ios/.gitignore diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Flutter/AppFrameworkInfo.plist b/example/router_example/ios/Flutter/AppFrameworkInfo.plist similarity index 96% rename from packages/stacked_localisation/stacked_localisation/example/ios/Flutter/AppFrameworkInfo.plist rename to example/router_example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..4f8d4d245 100644 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/router_example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 11.0 diff --git a/packages/stacked_themes/example/ios/Flutter/Debug.xcconfig b/example/router_example/ios/Flutter/Debug.xcconfig similarity index 58% rename from packages/stacked_themes/example/ios/Flutter/Debug.xcconfig rename to example/router_example/ios/Flutter/Debug.xcconfig index 05ecec519..ec97fc6f3 100644 --- a/packages/stacked_themes/example/ios/Flutter/Debug.xcconfig +++ b/example/router_example/ios/Flutter/Debug.xcconfig @@ -1,3 +1,2 @@ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/stacked_themes/example/ios/Flutter/Release.xcconfig b/example/router_example/ios/Flutter/Release.xcconfig similarity index 57% rename from packages/stacked_themes/example/ios/Flutter/Release.xcconfig rename to example/router_example/ios/Flutter/Release.xcconfig index c8fe6c11a..c4855bfe2 100644 --- a/packages/stacked_themes/example/ios/Flutter/Release.xcconfig +++ b/example/router_example/ios/Flutter/Release.xcconfig @@ -1,3 +1,2 @@ #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/stacked_themes/example/ios/Podfile b/example/router_example/ios/Podfile similarity index 98% rename from packages/stacked_themes/example/ios/Podfile rename to example/router_example/ios/Podfile index 1e8c3c90a..88359b225 100644 --- a/packages/stacked_themes/example/ios/Podfile +++ b/example/router_example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/stacked/example/ios/Runner.xcodeproj/project.pbxproj b/example/router_example/ios/Runner.xcodeproj/project.pbxproj similarity index 73% rename from packages/stacked/example/ios/Runner.xcodeproj/project.pbxproj rename to example/router_example/ios/Runner.xcodeproj/project.pbxproj index 548829e54..3b0300177 100644 --- a/packages/stacked/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/router_example/ios/Runner.xcodeproj/project.pbxproj @@ -3,17 +3,14 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ + 087D4595D28B86FF54F9941C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE2F0710F9508CE9E267371A /* Pods_Runner.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -26,8 +23,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -35,21 +30,23 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 13D973FF7A659DE29DFBA53D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 377E93FFD476638C7B9A0776 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BE2F0710F9508CE9E267371A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CB3C6C5960D76BE5490900A4 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -57,20 +54,25 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + 087D4595D28B86FF54F9941C /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 16A60DD6A01716BC8534A1D2 /* Frameworks */ = { + isa = PBXGroup; + children = ( + BE2F0710F9508CE9E267371A /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -84,6 +86,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + C7820955A436B53B42DB68D2 /* Pods */, + 16A60DD6A01716BC8534A1D2 /* Frameworks */, ); sourceTree = ""; }; @@ -118,6 +122,17 @@ name = "Supporting Files"; sourceTree = ""; }; + C7820955A436B53B42DB68D2 /* Pods */ = { + isa = PBXGroup; + children = ( + 13D973FF7A659DE29DFBA53D /* Pods-Runner.debug.xcconfig */, + 377E93FFD476638C7B9A0776 /* Pods-Runner.release.xcconfig */, + CB3C6C5960D76BE5490900A4 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -125,12 +140,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + A7C09855BCE08D7818924091 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 60B45487C52EA1C7CCDF405A /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -147,7 +164,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -191,20 +208,71 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 60B45487C52EA1C7CCDF405A /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseCoreExtension/FirebaseCoreExtension.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseCoreInternal/FirebaseCoreInternal.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseCrashlytics/FirebaseCrashlytics.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseInstallations/FirebaseInstallations.framework", + "${BUILT_PRODUCTS_DIR}/FirebaseSessions/FirebaseSessions.framework", + "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", + "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", + "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", + "${BUILT_PRODUCTS_DIR}/PromisesSwift/Promises.framework", + "${BUILT_PRODUCTS_DIR}/device_info_plus/device_info_plus.framework", + "${BUILT_PRODUCTS_DIR}/flutter_statusbarcolor_ns/flutter_statusbarcolor_ns.framework", + "${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework", + "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", + "${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework", + "${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCore.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreExtension.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreInternal.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCrashlytics.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseInstallations.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseSessions.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleDataTransport.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Promises.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_statusbarcolor_ns.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -217,6 +285,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + A7C09855BCE08D7818924091 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -253,7 +343,6 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -293,7 +382,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -315,7 +404,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -330,7 +422,6 @@ }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -376,7 +467,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -386,7 +477,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -426,7 +516,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -449,7 +539,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -476,7 +569,10 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", diff --git a/packages/stacked/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/router_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 71% rename from packages/stacked/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to example/router_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16e..919434a62 100644 --- a/packages/stacked/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/router_example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/router_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to example/router_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/router_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to example/router_example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/stacked_services/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/router_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 99% rename from packages/stacked_services/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to example/router_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..3db53b6e1 100644 --- a/packages/stacked_services/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/router_example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/router_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to example/router_example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/router_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to example/router_example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/stacked_hooks/example/ios/Runner/AppDelegate.swift b/example/router_example/ios/Runner/AppDelegate.swift similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/AppDelegate.swift rename to example/router_example/ios/Runner/AppDelegate.swift diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to example/router_example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/packages/stacked_hooks/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to example/router_example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/packages/stacked_hooks/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/router_example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to example/router_example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/packages/stacked_hooks/example/ios/Runner/Base.lproj/Main.storyboard b/example/router_example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Base.lproj/Main.storyboard rename to example/router_example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/stacked_hooks/example/ios/Runner/Info.plist b/example/router_example/ios/Runner/Info.plist similarity index 90% rename from packages/stacked_hooks/example/ios/Runner/Info.plist rename to example/router_example/ios/Runner/Info.plist index a060db61e..30d19575e 100644 --- a/packages/stacked_hooks/example/ios/Runner/Info.plist +++ b/example/router_example/ios/Runner/Info.plist @@ -11,7 +11,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - example + new_architecture CFBundlePackageType APPL CFBundleShortVersionString @@ -41,5 +41,9 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/packages/stacked_hooks/example/ios/Runner/Runner-Bridging-Header.h b/example/router_example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from packages/stacked_hooks/example/ios/Runner/Runner-Bridging-Header.h rename to example/router_example/ios/Runner/Runner-Bridging-Header.h diff --git a/example/router_example/ios/RunnerTests/RunnerTests.swift b/example/router_example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/example/router_example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example/router_example/lib/app/app.bottomsheets.dart b/example/router_example/lib/app/app.bottomsheets.dart new file mode 100644 index 000000000..df8a623d9 --- /dev/null +++ b/example/router_example/lib/app/app.bottomsheets.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedBottomsheetGenerator +// ************************************************************************** + +import 'package:stacked_services/stacked_services.dart'; + +import 'app.locator.dart'; +import '../ui/bottomsheets/generic_bottomsheet.dart'; + +enum BottomSheetType { + genericBottom, +} + +void setupBottomSheetUi() { + final bottomsheetService = exampleLocator(); + + final Map builders = { + BottomSheetType.genericBottom: (context, request, completer) => + GenericBottomSheet(request: request, completer: completer), + }; + + bottomsheetService.setCustomSheetBuilders(builders); +} diff --git a/example/router_example/lib/app/app.dart b/example/router_example/lib/app/app.dart new file mode 100644 index 000000000..2627619cd --- /dev/null +++ b/example/router_example/lib/app/app.dart @@ -0,0 +1,99 @@ +import 'package:example/services/epoch_service.dart'; +import 'package:example/services/factory_service.dart'; +import 'package:example/services/information_service.dart'; +import 'package:example/services/native_actions_service.dart'; +import 'package:example/services/shared_preferences_service.dart'; +import 'package:example/ui/bottom_nav/bottom_nav_example.dart'; +import 'package:example/ui/bottom_nav/favorites/favorites_view.dart'; +import 'package:example/ui/bottom_nav/favorites/favorites_viewmodel.dart'; +import 'package:example/ui/bottom_nav/history/history_view.dart'; +import 'package:example/ui/bottom_nav/history/history_viewmodel.dart'; +import 'package:example/ui/bottom_nav/profile/profile_view.dart'; +import 'package:example/ui/bottomsheets/generic_bottomsheet.dart'; +import 'package:example/ui/dialogs/basic_dialog.dart'; +import 'package:example/ui/form/example_form_view.dart'; +import 'package:example/ui/home/home_view.dart'; +import 'package:example/ui/multiple_futures_example/multiple_futures_example_view.dart'; +import 'package:example/ui/nonreactive/nonreactive_view.dart'; +import 'package:example/ui/stream_view/stream_counter_view.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; +import 'package:stacked_shared/stacked_shared.dart'; +import 'package:stacked_themes/stacked_themes.dart'; + +import 'custom_route_transition.dart'; + +@StackedApp( + bottomsheets: [ + StackedBottomsheet(classType: GenericBottomSheet), + ], + dialogs: [ + StackedDialog(classType: BasicDialog), + ], + routes: [ + MaterialRoute(page: HomeView, initial: true), + MaterialRoute(page: BottomNavExample, children: [ + RedirectRoute(path: '', redirectTo: 'favorites'), + AdaptiveRoute( + path: 'favourites', + page: FavoritesView, + children: [ + MaterialRoute(page: MultipleFuturesExampleView), + CustomRoute(page: HistoryView), + ], + ), + CustomRoute( + page: HistoryView, + transitionsBuilder: TransitionsBuilders.fadeIn, + ), + CupertinoRoute(page: ProfileView), + ]), + MaterialRoute(page: StreamCounterView), + MaterialRoute(page: ExampleFormView), + CustomRoute( + page: NonReactiveView, + transitionsBuilder: CustomRouteTransition.sharedAxis, + ), + ], + dependencies: [ + // Lazy singletons + LazySingleton(classType: DialogService), + LazySingleton(classType: BottomSheetService), + LazySingleton( + classType: RouterService, + ), + LazySingleton( + classType: NavigationService, + environments: {Environment.dev}, + instanceName: 'instance1', + ), + LazySingleton(classType: EpochService), + LazySingleton( + classType: ThemeService, + resolveUsing: ThemeService.getInstance, + ), + LazySingleton(classType: InformationService), + LazySingleton(classType: InformationService, instanceName: 'infoInstance1'), + FactoryWithParam(classType: FactoryService), + // Singletons + Singleton(classType: HistoryViewModel), + Singleton(classType: FavoritesViewModel), + + InitializableSingleton(classType: SharedPreferencesService), + // Presolve( + // classType: SharedPreferencesService, + // presolveUsing: SharedPreferencesService.getInstance, + // ), + InitializableSingleton(classType: NativeActionsService), + // Presolve( + // classType: NativeActionsService, + // presolveUsing: NativeActionsService.getInstance, + // ), + ], + logger: StackedLogger(), + locatorName: 'exampleLocator', + locatorSetupName: 'setupExampleLocator', +) +class App { + /// This class has no puporse besides housing the annotation that generates the required functionality +} diff --git a/example/router_example/lib/app/app.dialog.dart b/example/router_example/lib/app/app.dialog.dart new file mode 100644 index 000000000..4113beba4 --- /dev/null +++ b/example/router_example/lib/app/app.dialog.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedDialogGenerator +// ************************************************************************** + +import 'package:stacked_services/stacked_services.dart'; +import 'app.locator.dart'; + +import '../ui/dialogs/basic_dialog.dart'; + +enum DialogType { + basicDialog, +} + +void setupDialogUi() { + var dialogService = exampleLocator(); + + final builders = { + DialogType.basicDialog: (context, DialogRequest request, + void Function(DialogResponse) completer) => + BasicDialog(request: request, completer: completer), + }; + + dialogService.registerCustomDialogBuilders(builders); +} diff --git a/example/router_example/lib/app/app.dialogs.dart b/example/router_example/lib/app/app.dialogs.dart new file mode 100644 index 000000000..ae7251eba --- /dev/null +++ b/example/router_example/lib/app/app.dialogs.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedDialogGenerator +// ************************************************************************** + +import 'package:stacked_services/stacked_services.dart'; + +import 'app.locator.dart'; +import '../ui/dialogs/basic_dialog.dart'; + +enum DialogType { + basic, +} + +void setupDialogUi() { + final dialogService = exampleLocator(); + + final Map builders = { + DialogType.basic: (context, request, completer) => + BasicDialog(request: request, completer: completer), + }; + + dialogService.registerCustomDialogBuilders(builders); +} diff --git a/example/router_example/lib/app/app.locator.dart b/example/router_example/lib/app/app.locator.dart new file mode 100644 index 000000000..6923bf14c --- /dev/null +++ b/example/router_example/lib/app/app.locator.dart @@ -0,0 +1,65 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedLocatorGenerator +// ************************************************************************** + +// ignore_for_file: public_member_api_docs, implementation_imports, depend_on_referenced_packages + +import 'package:stacked_services/src/bottom_sheet/bottom_sheet_service.dart'; +import 'package:stacked_services/src/dialog/dialog_service.dart'; +import 'package:stacked_services/src/navigation/navigation_service.dart'; +import 'package:stacked_services/src/navigation/router_service.dart'; +import 'package:stacked_shared/stacked_shared.dart'; +import 'package:stacked_themes/src/theme_service.dart'; + +import '../services/epoch_service.dart'; +import '../services/factory_service.dart'; +import '../services/information_service.dart'; +import '../services/native_actions_service.dart'; +import '../services/shared_preferences_service.dart'; +import '../ui/bottom_nav/favorites/favorites_viewmodel.dart'; +import '../ui/bottom_nav/history/history_viewmodel.dart'; +import 'app.router.dart'; + +final exampleLocator = StackedLocator.instance; + +Future setupExampleLocator({ + String? environment, + EnvironmentFilter? environmentFilter, + StackedRouterWeb? stackedRouter, +}) async { +// Register environments + exampleLocator.registerEnvironment( + environment: environment, environmentFilter: environmentFilter); + +// Register dependencies + exampleLocator.registerLazySingleton(() => DialogService()); + exampleLocator.registerLazySingleton(() => BottomSheetService()); + exampleLocator.registerLazySingleton(() => RouterService()); + exampleLocator.registerLazySingleton(() => NavigationService(), + registerFor: {"dev"}, instanceName: 'instance1'); + exampleLocator.registerLazySingleton(() => EpochService()); + exampleLocator.registerLazySingleton(() => ThemeService.getInstance()); + exampleLocator.registerLazySingleton(() => InformationService()); + exampleLocator.registerLazySingleton(() => InformationService(), + instanceName: 'infoInstance1'); + exampleLocator.registerFactoryParam( + (param1, param2) => FactoryService(param1, data2: param2)); + exampleLocator.registerSingleton(HistoryViewModel()); + exampleLocator.registerSingleton(FavoritesViewModel()); + final sharedPreferencesService = SharedPreferencesService(); + await sharedPreferencesService.init(); + exampleLocator.registerSingleton(sharedPreferencesService); + + final nativeActionsService = NativeActionsService(); + await nativeActionsService.init(); + exampleLocator.registerSingleton(nativeActionsService); + + if (stackedRouter == null) { + throw Exception( + 'Stacked is building to use the Router (Navigator 2.0) navigation but no stackedRouter is supplied. Pass the stackedRouter to the setupLocator function in main.dart'); + } + + exampleLocator().setRouter(stackedRouter); +} diff --git a/packages/stacked_generator/lib/src/generators/logging/logger_class_content.dart b/example/router_example/lib/app/app.logger.dart similarity index 54% rename from packages/stacked_generator/lib/src/generators/logging/logger_class_content.dart rename to example/router_example/lib/app/app.logger.dart index cd92f97c7..26ab3f947 100644 --- a/packages/stacked_generator/lib/src/generators/logging/logger_class_content.dart +++ b/example/router_example/lib/app/app.logger.dart @@ -1,10 +1,10 @@ -const String LogHelperNameKey = 'logHelperName'; -const String MultiLoggerImports = 'MultiLoggerImport'; -const String MultipleLoggerOutput = 'MultiLoggerList'; -const String DisableConsoleOutputInRelease = 'MultiLoggerList'; +// GENERATED CODE - DO NOT MODIFY BY HAND -const String loggerClassContent = ''' -// ignore_for_file: avoid_print +// ************************************************************************** +// StackedLoggerGenerator +// ************************************************************************** + +// ignore_for_file: avoid_print, depend_on_referenced_packages /// Maybe this should be generated for the user as well? /// @@ -12,9 +12,6 @@ const String loggerClassContent = ''' import 'package:flutter/foundation.dart'; import 'package:logger/logger.dart'; -$MultiLoggerImports - - class SimpleLogPrinter extends LogPrinter { final String className; final bool printCallingFunctionName; @@ -37,10 +34,10 @@ class SimpleLogPrinter extends LogPrinter { var methodName = _getMethodName(); var methodNameSection = - printCallingFunctionName && methodName != null ? ' | \$methodName ' : ''; + printCallingFunctionName && methodName != null ? ' | $methodName' : ''; var stackLog = event.stackTrace.toString(); var output = - '\$emoji \$className\$methodNameSection - \${event.message}\${printCallStack ? '\\nSTACKTRACE:\\n\$stackLog' : ''}'; + '$emoji $className$methodNameSection - ${event.message}${event.error != null ? '\nERROR: ${event.error}\n' : ''}${printCallStack ? '\nSTACKTRACE:\n$stackLog' : ''}'; if (exludeLogsFromClasses .any((excludeClass) => className == excludeClass) || @@ -49,7 +46,7 @@ class SimpleLogPrinter extends LogPrinter { final pattern = RegExp('.{1,800}'); // 800 is the size of each chunk List result = []; - for (var line in output.split('\\n')) { + for (var line in output.split('\n')) { result.addAll(pattern.allMatches(line).map((match) { if (kReleaseMode) { return match.group(0)!; @@ -64,25 +61,56 @@ class SimpleLogPrinter extends LogPrinter { String? _getMethodName() { try { - var currentStack = StackTrace.current; - var formattedStacktrace = _formatStackTrace(currentStack, 3); - - var realFirstLine = formattedStacktrace - ?.firstWhere((line) => line.contains(className), orElse: () => ""); - - var methodName = realFirstLine?.replaceAll('\$className.', ''); - return methodName; + final currentStack = StackTrace.current; + final formattedStacktrace = _formatStackTrace(currentStack, 3); + if (kIsWeb) { + final classNameParts = _splitClassNameWords(className); + return _findMostMatchedTrace(formattedStacktrace!, classNameParts) + .split(' ') + .last; + } else { + final realFirstLine = formattedStacktrace + ?.firstWhere((line) => line.contains(className), orElse: () => ""); + + final methodName = realFirstLine?.replaceAll('$className.', ''); + return methodName; + } } catch (e) { // There's no deliberate function call from our code so we return null; return null; } } + + List _splitClassNameWords(String className) { + return className + .split(RegExp(r'(?=[A-Z])')) + .map((e) => e.toLowerCase()) + .toList(); + } + + /// When the faulty word exists in the begging this method will not be very usefull + String _findMostMatchedTrace( + List stackTraces, List keyWords) { + String match = stackTraces.firstWhere( + (trace) => _doesTraceContainsAllKeywords(trace, keyWords), + orElse: () => ''); + if (match.isEmpty) { + match = _findMostMatchedTrace( + stackTraces, keyWords.sublist(0, keyWords.length - 1)); + } + return match; + } + + bool _doesTraceContainsAllKeywords(String stackTrace, List keywords) { + final formattedKeywordsAsRegex = RegExp(keywords.join('.*')); + return stackTrace.contains(formattedKeywordsAsRegex); + } } -final stackTraceRegex = RegExp(r'#[0-9]+[\\s]+(.+) \\(([^\\s]+)\\)'); +final stackTraceRegex = RegExp(r'#[0-9]+[\s]+(.+) \(([^\s]+)\)'); List? _formatStackTrace(StackTrace stackTrace, int methodCount) { - var lines = stackTrace.toString().split('\\n'); + var lines = stackTrace.toString().split('\n'); var formatted = []; var count = 0; @@ -92,7 +120,7 @@ List? _formatStackTrace(StackTrace stackTrace, int methodCount) { if (match.group(2)!.startsWith('package:logger')) { continue; } - var newLine = ("\${match.group(1)}"); + var newLine = ("${match.group(1)}"); formatted.add(newLine.replaceAll('', '()')); if (++count == methodCount) { break; @@ -109,23 +137,7 @@ List? _formatStackTrace(StackTrace stackTrace, int methodCount) { } } -class MultipleLoggerOutput extends LogOutput { - final List logOutputs; - MultipleLoggerOutput(this.logOutputs); - - @override - void output(OutputEvent event) { - for (var logOutput in logOutputs) { - try { - logOutput.output(event); - } catch (e) { - print('Log output failed'); - } - } - } -} - -Logger $LogHelperNameKey( +Logger getLogger( String className, { bool printCallingFunctionName = true, bool printCallstack = false, @@ -140,11 +152,8 @@ Logger $LogHelperNameKey( showOnlyClass: showOnlyClass, exludeLogsFromClasses: exludeLogsFromClasses, ), - output: MultipleLoggerOutput([ - $DisableConsoleOutputInRelease - ConsoleOutput(), - $MultipleLoggerOutput + output: MultiOutput([ + if (!kReleaseMode) ConsoleOutput(), ]), ); } -'''; diff --git a/example/router_example/lib/app/app.router.dart b/example/router_example/lib/app/app.router.dart new file mode 100644 index 000000000..eebd5ee1a --- /dev/null +++ b/example/router_example/lib/app/app.router.dart @@ -0,0 +1,608 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedRouterGenerator +// ************************************************************************** + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:flutter/foundation.dart' as _i15; +import 'package:flutter/material.dart' as _i13; +import 'package:stacked/stacked.dart' as _i12; +import 'package:stacked_services/stacked_services.dart' as _i11; + +import '../datamodels/clashable_one.dart' as _i16; +import '../datamodels/clashable_two.dart' as _i17; +import '../datamodels/home_type.dart' as _i1; +import '../ui/bottom_nav/bottom_nav_example.dart' as _i3; +import '../ui/bottom_nav/favorites/favorites_view.dart' as _i7; +import '../ui/bottom_nav/history/history_view.dart' as _i8; +import '../ui/bottom_nav/profile/profile_view.dart' as _i9; +import '../ui/form/example_form_view.dart' as _i5; +import '../ui/home/home_view.dart' as _i2; +import '../ui/multiple_futures_example/multiple_futures_example_view.dart' + as _i10; +import '../ui/nonreactive/nonreactive_view.dart' as _i6; +import '../ui/stream_view/stream_counter_view.dart' as _i4; +import 'custom_route_transition.dart' as _i14; + +final stackedRouter = + StackedRouterWeb(navigatorKey: _i11.StackedService.navigatorKey); + +class StackedRouterWeb extends _i12.RootStackRouter { + StackedRouterWeb({_i13.GlobalKey<_i13.NavigatorState>? navigatorKey}) + : super(navigatorKey); + + @override + final Map pagesMap = { + HomeViewRoute.name: (routeData) { + final args = + routeData.argsAs(orElse: () => const HomeViewArgs()); + return _i12.MaterialPageX( + routeData: routeData, + child: _i2.HomeView( + key: args.key, + title: args.title, + isLoggedIn: args.isLoggedIn, + clashableGetter: args.clashableGetter, + homeTypes: args.homeTypes, + ), + ); + }, + BottomNavExampleRoute.name: (routeData) { + return _i12.MaterialPageX( + routeData: routeData, + child: const _i3.BottomNavExample(), + ); + }, + StreamCounterViewRoute.name: (routeData) { + final args = routeData.argsAs(); + return _i12.MaterialPageX( + routeData: routeData, + child: _i4.StreamCounterView( + key: args.key, + clashableTwo: args.clashableTwo, + ), + ); + }, + ExampleFormViewRoute.name: (routeData) { + final args = routeData.argsAs(); + return _i12.MaterialPageX( + routeData: routeData, + child: _i5.ExampleFormView( + key: args.key, + clashableOne: args.clashableOne, + ), + ); + }, + NonReactiveViewRoute.name: (routeData) { + return _i12.CustomPage( + routeData: routeData, + child: const _i6.NonReactiveView(), + transitionsBuilder: _i14.CustomRouteTransition.sharedAxis, + opaque: true, + barrierDismissible: false, + ); + }, + FavoritesViewRoute.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const FavoritesViewArgs()); + return _i12.AdaptivePage( + routeData: routeData, + child: _i7.FavoritesView( + key: args.key, + id: args.id, + ), + opaque: true, + ); + }, + HistoryViewRoute.name: (routeData) { + return _i12.CustomPage( + routeData: routeData, + child: const _i8.HistoryView(), + opaque: true, + barrierDismissible: false, + ); + }, + ProfileViewRoute.name: (routeData) { + return _i12.CupertinoPageX( + routeData: routeData, + child: const _i9.ProfileView(), + ); + }, + MultipleFuturesExampleViewRoute.name: (routeData) { + return _i12.MaterialPageX( + routeData: routeData, + child: const _i10.MultipleFuturesExampleView(), + ); + }, + }; + + @override + List<_i12.RouteConfig> get routes => [ + _i12.RouteConfig( + HomeViewRoute.name, + path: '/', + ), + _i12.RouteConfig( + BottomNavExampleRoute.name, + path: '/bottom-nav-example', + children: [ + _i12.RouteConfig( + '#redirect', + path: '', + parent: BottomNavExampleRoute.name, + redirectTo: 'favorites', + fullMatch: true, + ), + _i12.RouteConfig( + FavoritesViewRoute.name, + path: 'favourites', + parent: BottomNavExampleRoute.name, + children: [ + _i12.RouteConfig( + MultipleFuturesExampleViewRoute.name, + path: 'multiple-futures-example-view', + parent: FavoritesViewRoute.name, + ), + _i12.RouteConfig( + HistoryViewRoute.name, + path: 'history-view', + parent: FavoritesViewRoute.name, + ), + ], + ), + _i12.RouteConfig( + HistoryViewRoute.name, + path: 'history-view', + parent: BottomNavExampleRoute.name, + ), + _i12.RouteConfig( + ProfileViewRoute.name, + path: 'profile-view', + parent: BottomNavExampleRoute.name, + ), + ], + ), + _i12.RouteConfig( + StreamCounterViewRoute.name, + path: '/stream-counter-view', + ), + _i12.RouteConfig( + ExampleFormViewRoute.name, + path: '/example-form-view', + ), + _i12.RouteConfig( + NonReactiveViewRoute.name, + path: '/non-reactive-view', + ), + ]; +} + +/// generated route for +/// [_i2.HomeView] +class HomeViewRoute extends _i12.PageRouteInfo { + HomeViewRoute({ + _i15.Key? key, + String? title = 'hello', + bool? isLoggedIn = false, + _i16.Clashable Function(String)? clashableGetter, + List<_i1.HomeType> homeTypes = const [ + _i1.HomeType.apartment, + _i1.HomeType.house + ], + }) : super( + HomeViewRoute.name, + path: '/', + args: HomeViewArgs( + key: key, + title: title, + isLoggedIn: isLoggedIn, + clashableGetter: clashableGetter, + homeTypes: homeTypes, + ), + ); + + static const String name = 'HomeView'; +} + +class HomeViewArgs { + const HomeViewArgs({ + this.key, + this.title = 'hello', + this.isLoggedIn = false, + this.clashableGetter, + this.homeTypes = const [_i1.HomeType.apartment, _i1.HomeType.house], + }); + + final _i15.Key? key; + + final String? title; + + final bool? isLoggedIn; + + final _i16.Clashable Function(String)? clashableGetter; + + final List<_i1.HomeType> homeTypes; + + @override + String toString() { + return 'HomeViewArgs{key: $key, title: $title, isLoggedIn: $isLoggedIn, clashableGetter: $clashableGetter, homeTypes: $homeTypes}'; + } +} + +/// generated route for +/// [_i3.BottomNavExample] +class BottomNavExampleRoute extends _i12.PageRouteInfo { + const BottomNavExampleRoute({List<_i12.PageRouteInfo>? children}) + : super( + BottomNavExampleRoute.name, + path: '/bottom-nav-example', + initialChildren: children, + ); + + static const String name = 'BottomNavExample'; +} + +/// generated route for +/// [_i4.StreamCounterView] +class StreamCounterViewRoute extends _i12.PageRouteInfo { + StreamCounterViewRoute({ + _i15.Key? key, + required List<_i17.Clashable> clashableTwo, + }) : super( + StreamCounterViewRoute.name, + path: '/stream-counter-view', + args: StreamCounterViewArgs( + key: key, + clashableTwo: clashableTwo, + ), + ); + + static const String name = 'StreamCounterView'; +} + +class StreamCounterViewArgs { + const StreamCounterViewArgs({ + this.key, + required this.clashableTwo, + }); + + final _i15.Key? key; + + final List<_i17.Clashable> clashableTwo; + + @override + String toString() { + return 'StreamCounterViewArgs{key: $key, clashableTwo: $clashableTwo}'; + } +} + +/// generated route for +/// [_i5.ExampleFormView] +class ExampleFormViewRoute extends _i12.PageRouteInfo { + ExampleFormViewRoute({ + _i15.Key? key, + required _i16.Clashable clashableOne, + }) : super( + ExampleFormViewRoute.name, + path: '/example-form-view', + args: ExampleFormViewArgs( + key: key, + clashableOne: clashableOne, + ), + ); + + static const String name = 'ExampleFormView'; +} + +class ExampleFormViewArgs { + const ExampleFormViewArgs({ + this.key, + required this.clashableOne, + }); + + final _i15.Key? key; + + final _i16.Clashable clashableOne; + + @override + String toString() { + return 'ExampleFormViewArgs{key: $key, clashableOne: $clashableOne}'; + } +} + +/// generated route for +/// [_i6.NonReactiveView] +class NonReactiveViewRoute extends _i12.PageRouteInfo { + const NonReactiveViewRoute() + : super( + NonReactiveViewRoute.name, + path: '/non-reactive-view', + ); + + static const String name = 'NonReactiveView'; +} + +/// generated route for +/// [_i7.FavoritesView] +class FavoritesViewRoute extends _i12.PageRouteInfo { + FavoritesViewRoute({ + _i15.Key? key, + String? id, + List<_i12.PageRouteInfo>? children, + }) : super( + FavoritesViewRoute.name, + path: 'favourites', + args: FavoritesViewArgs( + key: key, + id: id, + ), + initialChildren: children, + ); + + static const String name = 'FavoritesView'; +} + +class FavoritesViewArgs { + const FavoritesViewArgs({ + this.key, + this.id, + }); + + final _i15.Key? key; + + final String? id; + + @override + String toString() { + return 'FavoritesViewArgs{key: $key, id: $id}'; + } +} + +/// generated route for +/// [_i8.HistoryView] +class HistoryViewRoute extends _i12.PageRouteInfo { + const HistoryViewRoute() + : super( + HistoryViewRoute.name, + path: 'history-view', + ); + + static const String name = 'HistoryView'; +} + +/// generated route for +/// [_i9.ProfileView] +class ProfileViewRoute extends _i12.PageRouteInfo { + const ProfileViewRoute() + : super( + ProfileViewRoute.name, + path: 'profile-view', + ); + + static const String name = 'ProfileView'; +} + +/// generated route for +/// [_i10.MultipleFuturesExampleView] +class MultipleFuturesExampleViewRoute extends _i12.PageRouteInfo { + const MultipleFuturesExampleViewRoute() + : super( + MultipleFuturesExampleViewRoute.name, + path: 'multiple-futures-example-view', + ); + + static const String name = 'MultipleFuturesExampleView'; +} + +extension RouterStateExtension on _i11.RouterService { + Future navigateToHomeView({ + _i15.Key? key, + String? title = 'hello', + bool? isLoggedIn = false, + _i16.Clashable Function(String)? clashableGetter, + List<_i1.HomeType> homeTypes = const [ + _i1.HomeType.apartment, + _i1.HomeType.house + ], + void Function(_i12.NavigationFailure)? onFailure, + }) async { + return navigateTo( + HomeViewRoute( + key: key, + title: title, + isLoggedIn: isLoggedIn, + clashableGetter: clashableGetter, + homeTypes: homeTypes, + ), + onFailure: onFailure, + ); + } + + Future navigateToBottomNavExample( + {void Function(_i12.NavigationFailure)? onFailure}) async { + return navigateTo( + const BottomNavExampleRoute(), + onFailure: onFailure, + ); + } + + Future navigateToStreamCounterView({ + _i15.Key? key, + required List<_i17.Clashable> clashableTwo, + void Function(_i12.NavigationFailure)? onFailure, + }) async { + return navigateTo( + StreamCounterViewRoute( + key: key, + clashableTwo: clashableTwo, + ), + onFailure: onFailure, + ); + } + + Future navigateToExampleFormView({ + _i15.Key? key, + required _i16.Clashable clashableOne, + void Function(_i12.NavigationFailure)? onFailure, + }) async { + return navigateTo( + ExampleFormViewRoute( + key: key, + clashableOne: clashableOne, + ), + onFailure: onFailure, + ); + } + + Future navigateToNonReactiveView( + {void Function(_i12.NavigationFailure)? onFailure}) async { + return navigateTo( + const NonReactiveViewRoute(), + onFailure: onFailure, + ); + } + + Future navigateToFavoritesView({ + _i15.Key? key, + String? id, + void Function(_i12.NavigationFailure)? onFailure, + }) async { + return navigateTo( + FavoritesViewRoute( + key: key, + id: id, + ), + onFailure: onFailure, + ); + } + + Future navigateToHistoryView( + {void Function(_i12.NavigationFailure)? onFailure}) async { + return navigateTo( + const HistoryViewRoute(), + onFailure: onFailure, + ); + } + + Future navigateToProfileView( + {void Function(_i12.NavigationFailure)? onFailure}) async { + return navigateTo( + const ProfileViewRoute(), + onFailure: onFailure, + ); + } + + Future navigateToMultipleFuturesExampleView( + {void Function(_i12.NavigationFailure)? onFailure}) async { + return navigateTo( + const MultipleFuturesExampleViewRoute(), + onFailure: onFailure, + ); + } + + Future replaceWithHomeView({ + _i15.Key? key, + String? title = 'hello', + bool? isLoggedIn = false, + _i16.Clashable Function(String)? clashableGetter, + List<_i1.HomeType> homeTypes = const [ + _i1.HomeType.apartment, + _i1.HomeType.house + ], + void Function(_i12.NavigationFailure)? onFailure, + }) async { + return replaceWith( + HomeViewRoute( + key: key, + title: title, + isLoggedIn: isLoggedIn, + clashableGetter: clashableGetter, + homeTypes: homeTypes, + ), + onFailure: onFailure, + ); + } + + Future replaceWithBottomNavExample( + {void Function(_i12.NavigationFailure)? onFailure}) async { + return replaceWith( + const BottomNavExampleRoute(), + onFailure: onFailure, + ); + } + + Future replaceWithStreamCounterView({ + _i15.Key? key, + required List<_i17.Clashable> clashableTwo, + void Function(_i12.NavigationFailure)? onFailure, + }) async { + return replaceWith( + StreamCounterViewRoute( + key: key, + clashableTwo: clashableTwo, + ), + onFailure: onFailure, + ); + } + + Future replaceWithExampleFormView({ + _i15.Key? key, + required _i16.Clashable clashableOne, + void Function(_i12.NavigationFailure)? onFailure, + }) async { + return replaceWith( + ExampleFormViewRoute( + key: key, + clashableOne: clashableOne, + ), + onFailure: onFailure, + ); + } + + Future replaceWithNonReactiveView( + {void Function(_i12.NavigationFailure)? onFailure}) async { + return replaceWith( + const NonReactiveViewRoute(), + onFailure: onFailure, + ); + } + + Future replaceWithFavoritesView({ + _i15.Key? key, + String? id, + void Function(_i12.NavigationFailure)? onFailure, + }) async { + return replaceWith( + FavoritesViewRoute( + key: key, + id: id, + ), + onFailure: onFailure, + ); + } + + Future replaceWithHistoryView( + {void Function(_i12.NavigationFailure)? onFailure}) async { + return replaceWith( + const HistoryViewRoute(), + onFailure: onFailure, + ); + } + + Future replaceWithProfileView( + {void Function(_i12.NavigationFailure)? onFailure}) async { + return replaceWith( + const ProfileViewRoute(), + onFailure: onFailure, + ); + } + + Future replaceWithMultipleFuturesExampleView( + {void Function(_i12.NavigationFailure)? onFailure}) async { + return replaceWith( + const MultipleFuturesExampleViewRoute(), + onFailure: onFailure, + ); + } +} diff --git a/example/router_example/lib/app/custom_route_transition.dart b/example/router_example/lib/app/custom_route_transition.dart new file mode 100644 index 000000000..bc3f6ccf9 --- /dev/null +++ b/example/router_example/lib/app/custom_route_transition.dart @@ -0,0 +1,14 @@ +import 'package:animations/animations.dart'; +import 'package:flutter/material.dart'; + +class CustomRouteTransition { + static Widget sharedAxis(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return SharedAxisTransition( + animation: animation, + secondaryAnimation: secondaryAnimation, + transitionType: SharedAxisTransitionType.scaled, + child: child, + ); + } +} diff --git a/example/router_example/lib/datamodels/clashable_one.dart b/example/router_example/lib/datamodels/clashable_one.dart new file mode 100644 index 000000000..8b0ac1aac --- /dev/null +++ b/example/router_example/lib/datamodels/clashable_one.dart @@ -0,0 +1,5 @@ +class Clashable { + final String name; + + const Clashable(this.name); +} diff --git a/example/router_example/lib/datamodels/clashable_two.dart b/example/router_example/lib/datamodels/clashable_two.dart new file mode 100644 index 000000000..67bfb757f --- /dev/null +++ b/example/router_example/lib/datamodels/clashable_two.dart @@ -0,0 +1,5 @@ +class Clashable { + final int age; + + const Clashable(this.age); +} diff --git a/example/router_example/lib/datamodels/home_type.dart b/example/router_example/lib/datamodels/home_type.dart new file mode 100644 index 000000000..ee39e4bad --- /dev/null +++ b/example/router_example/lib/datamodels/home_type.dart @@ -0,0 +1 @@ +enum HomeType { apartment, house } diff --git a/example/router_example/lib/datamodels/human.dart b/example/router_example/lib/datamodels/human.dart new file mode 100644 index 000000000..318afdab1 --- /dev/null +++ b/example/router_example/lib/datamodels/human.dart @@ -0,0 +1,6 @@ +class Human { + final String? name; + final String? surname; + + Human({this.name, this.surname}); +} diff --git a/example/router_example/lib/main.dart b/example/router_example/lib/main.dart new file mode 100644 index 000000000..39902d0b5 --- /dev/null +++ b/example/router_example/lib/main.dart @@ -0,0 +1,32 @@ +import 'package:example/app/app.router.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked_shared/stacked_shared.dart'; +import 'package:url_strategy/url_strategy.dart'; + +import 'app/app.locator.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + setPathUrlStrategy(); + await setupExampleLocator( + environment: Environment.dev, + stackedRouter: stackedRouter, + ); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + title: 'Flutter Demo', + routerDelegate: stackedRouter.delegate(), + routeInformationParser: stackedRouter.defaultRouteParser(), + theme: ThemeData( + primarySwatch: Colors.blue, + ), + ); + } +} diff --git a/example/router_example/lib/services/epoch_service.dart b/example/router_example/lib/services/epoch_service.dart new file mode 100644 index 000000000..8fd0e1852 --- /dev/null +++ b/example/router_example/lib/services/epoch_service.dart @@ -0,0 +1,15 @@ +class EpochService { + Stream epochUpdatesNumbers() async* { + while (true) { + await Future.delayed(const Duration(seconds: 2)); + yield DateTime.now().millisecondsSinceEpoch; + } + } + + Stream epochUpdateNumbersQuickly() async* { + while (true) { + await Future.delayed(const Duration(milliseconds: 200)); + yield DateTime.now().millisecondsSinceEpoch; + } + } +} diff --git a/example/router_example/lib/services/factory_service.dart b/example/router_example/lib/services/factory_service.dart new file mode 100644 index 000000000..cad2134da --- /dev/null +++ b/example/router_example/lib/services/factory_service.dart @@ -0,0 +1,8 @@ +import 'package:stacked_shared/stacked_shared.dart'; + +class FactoryService { + final String? data1; + final double? data2; + + FactoryService(@factoryParam this.data1, {@factoryParam this.data2}); +} diff --git a/example/router_example/lib/services/iepoch_service.dart b/example/router_example/lib/services/iepoch_service.dart new file mode 100644 index 000000000..8e9c64fa3 --- /dev/null +++ b/example/router_example/lib/services/iepoch_service.dart @@ -0,0 +1 @@ +abstract class IEpochService {} diff --git a/example/router_example/lib/services/information_service.dart b/example/router_example/lib/services/information_service.dart new file mode 100644 index 000000000..9bd57f5fc --- /dev/null +++ b/example/router_example/lib/services/information_service.dart @@ -0,0 +1,16 @@ +import 'package:stacked/stacked.dart'; + +class InformationService with ListenableServiceMixin { + int _postCount = 0; + int get postCount => _postCount; + + void updatePostCount() { + _postCount++; + notifyListeners(); + } + + void resetCount() { + _postCount = 0; + notifyListeners(); + } +} diff --git a/example/router_example/lib/services/native_actions_service.dart b/example/router_example/lib/services/native_actions_service.dart new file mode 100644 index 000000000..768512e46 --- /dev/null +++ b/example/router_example/lib/services/native_actions_service.dart @@ -0,0 +1,65 @@ +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/foundation.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:stacked/stacked_annotations.dart'; + +class NativeActionsService implements InitializableDependency { + late PackageInfo _packageInfo; + + String _deviceId = ''; + String _operativeSystem = ''; + + String get appVersion => + '${_packageInfo.version}-${_packageInfo.buildNumber}'; + + String get pureVersion => _packageInfo.version; + + String get bundleId => _packageInfo.packageName; + + String get deviceId => _deviceId; + + String get operativeSystem => _operativeSystem; + + @override + Future init() async { + _packageInfo = await PackageInfo.fromPlatform(); + + final deviceInfoPlugin = DeviceInfoPlugin(); + + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + final info = await deviceInfoPlugin.iosInfo; + _deviceId = '${info.identifierForVendor}'; + _operativeSystem = '${info.systemName} ${info.systemVersion}'; + break; + case TargetPlatform.android: + final info = await deviceInfoPlugin.androidInfo; + _deviceId = info.serialNumber; + _operativeSystem = + '${info.version.release}, SDK:${info.version.sdkInt}'; + break; + case TargetPlatform.macOS: + // When running on browser is getting wrong data + if (kIsWeb) return; + + final info = await deviceInfoPlugin.macOsInfo; + _deviceId = '${info.systemGUID}'; + _operativeSystem = '${info.hostName} ${info.osRelease}'; + break; + case TargetPlatform.windows: + final info = await deviceInfoPlugin.windowsInfo; + _deviceId = info.deviceId; + _operativeSystem = '${info.productName} [${info.releaseId}]'; + break; + case TargetPlatform.linux: + final info = await deviceInfoPlugin.linuxInfo; + _deviceId = '${info.machineId}'; + _operativeSystem = info.prettyName; + break; + default: + final info = await deviceInfoPlugin.webBrowserInfo; + _deviceId = '${info.vendor}'; + _operativeSystem = info.browserName.name; + } + } +} diff --git a/example/router_example/lib/services/shared_preferences_service.dart b/example/router_example/lib/services/shared_preferences_service.dart new file mode 100644 index 000000000..9497f8d0b --- /dev/null +++ b/example/router_example/lib/services/shared_preferences_service.dart @@ -0,0 +1,58 @@ +import 'package:example/app/app.logger.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:stacked/stacked_annotations.dart'; + +class SharedPreferencesService implements InitializableDependency { + static const _isUserLoggedInKey = 'isUserLoggedIn'; + + final bool enableLogs; + SharedPreferencesService({this.enableLogs = false}); + + final _log = getLogger('SharedPreferencesService'); + + late SharedPreferences _preferences; + + @override + Future init() async { + _log.d('Initialized'); + _preferences = await SharedPreferences.getInstance(); + } + + bool get isUserLoggedIn => + (getFromDisk(_isUserLoggedInKey) as bool?) ?? false; + + set isUserLoggedIn(bool value) => saveToDisk(_isUserLoggedInKey, value); + + Set getKeys() => _preferences.getKeys(); + + Object? getFromDisk(String key) { + final value = _preferences.get(key); + if (enableLogs) _log.v('key:$key value:$value'); + return value; + } + + void saveToDisk(String key, dynamic content) { + if (enableLogs) _log.v('key:$key value:$content'); + + if (content is String) { + _preferences.setString(key, content); + } + if (content is bool) { + _preferences.setBool(key, content); + } + if (content is int) { + _preferences.setInt(key, content); + } + if (content is double) { + _preferences.setDouble(key, content); + } + if (content is List) { + _preferences.setStringList(key, content); + } + } + + void dispose() { + _log.i(''); + _preferences.clear(); + } +} diff --git a/example/router_example/lib/ui/bottom_nav/bottom_nav_example.dart b/example/router_example/lib/ui/bottom_nav/bottom_nav_example.dart new file mode 100644 index 000000000..7b681b987 --- /dev/null +++ b/example/router_example/lib/ui/bottom_nav/bottom_nav_example.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'bottom_nav_example_viewmodel.dart'; + +class BottomNavExample extends StatefulWidget { + const BottomNavExample({Key? key}) : super(key: key); + + @override + BottomNavExampleState createState() => BottomNavExampleState(); +} + +class BottomNavExampleState extends State { + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + builder: (context, viewModel, child) => Scaffold( + body: const NestedRouter(), + bottomNavigationBar: BottomNavigationBar( + elevation: 6, + backgroundColor: Colors.white, + currentIndex: viewModel.currentIndex, + onTap: viewModel.handleNavigation, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.favorite), + label: 'Favorites', + ), + BottomNavigationBarItem( + icon: Icon(Icons.history), + label: 'History', + ), + BottomNavigationBarItem( + icon: Icon(Icons.person), + label: 'Profile', + ), + ], + ), + ), + viewModelBuilder: () => BottomNavExampleViewModel(), + ); + } +} diff --git a/example/router_example/lib/ui/bottom_nav/bottom_nav_example_viewmodel.dart b/example/router_example/lib/ui/bottom_nav/bottom_nav_example_viewmodel.dart new file mode 100644 index 000000000..1e347ef5d --- /dev/null +++ b/example/router_example/lib/ui/bottom_nav/bottom_nav_example_viewmodel.dart @@ -0,0 +1,31 @@ +import 'package:example/app/app.locator.dart'; +import 'package:example/app/app.logger.dart'; +import 'package:example/app/app.router.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; + +class BottomNavExampleViewModel extends IndexTrackingViewModel { + final log = getLogger('BottomNavExampleViewModel'); + final _routerService = exampleLocator(); + + BottomNavExampleViewModel() { + setCurrentWebPageIndex(_routerService); + } + + void handleNavigation(int index) { + log.i('handleNavigation: $index'); + setIndex(index); + switch (index) { + case 0: + _routerService.navigateTo(FavoritesViewRoute()); + break; + case 1: + _routerService.navigateTo(const HistoryViewRoute()); + break; + case 2: + _routerService.navigateTo(const ProfileViewRoute()); + break; + default: + } + } +} diff --git a/example/router_example/lib/ui/bottom_nav/favorites/favorites_view.dart b/example/router_example/lib/ui/bottom_nav/favorites/favorites_view.dart new file mode 100644 index 000000000..9bb45fa22 --- /dev/null +++ b/example/router_example/lib/ui/bottom_nav/favorites/favorites_view.dart @@ -0,0 +1,29 @@ +import 'package:example/app/app.locator.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'favorites_viewmodel.dart'; + +class FavoritesView extends StatelessWidget { + final String? id; + const FavoritesView({Key? key, this.id}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + builder: (context, viewModel, child) => Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () => viewModel.incrementCounter(), + ), + body: Center( + child: Text( + viewModel.counter.toString(), + style: const TextStyle(fontSize: 30), + ))), + viewModelBuilder: () => exampleLocator(), + onViewModelReady: (viewModel) => viewModel.setCounterTo999(), + disposeViewModel: false, + fireOnViewModelReadyOnce: true, + ); + } +} diff --git a/example/router_example/lib/ui/bottom_nav/favorites/favorites_viewmodel.dart b/example/router_example/lib/ui/bottom_nav/favorites/favorites_viewmodel.dart new file mode 100644 index 000000000..0c50eb4ca --- /dev/null +++ b/example/router_example/lib/ui/bottom_nav/favorites/favorites_viewmodel.dart @@ -0,0 +1,16 @@ +import 'package:stacked/stacked.dart'; + +class FavoritesViewModel extends BaseViewModel { + int _counter = 0; + int get counter => _counter; + + void incrementCounter() { + _counter++; + notifyListeners(); + } + + void setCounterTo999() { + _counter = 999; + notifyListeners(); + } +} diff --git a/example/router_example/lib/ui/bottom_nav/history/history_view.dart b/example/router_example/lib/ui/bottom_nav/history/history_view.dart new file mode 100644 index 000000000..608a7c6c7 --- /dev/null +++ b/example/router_example/lib/ui/bottom_nav/history/history_view.dart @@ -0,0 +1,23 @@ +import 'package:example/app/app.locator.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'history_viewmodel.dart'; + +class HistoryView extends StatelessWidget { + const HistoryView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + initialiseSpecialViewModelsOnce: true, + disposeViewModel: false, + builder: (context, viewModel, child) => Scaffold( + body: Center( + child: viewModel.isBusy + ? const CircularProgressIndicator() + : Text(viewModel.data.toString()))), + viewModelBuilder: () => exampleLocator(), + ); + } +} diff --git a/example/router_example/lib/ui/bottom_nav/history/history_viewmodel.dart b/example/router_example/lib/ui/bottom_nav/history/history_viewmodel.dart new file mode 100644 index 000000000..092637e13 --- /dev/null +++ b/example/router_example/lib/ui/bottom_nav/history/history_viewmodel.dart @@ -0,0 +1,9 @@ +import 'package:stacked/stacked.dart'; + +class HistoryViewModel extends FutureViewModel { + @override + Future futureToRun() async { + await Future.delayed(const Duration(seconds: 2)); + return 100; + } +} diff --git a/example/router_example/lib/ui/bottom_nav/profile/profile_view.dart b/example/router_example/lib/ui/bottom_nav/profile/profile_view.dart new file mode 100644 index 000000000..09ceb6782 --- /dev/null +++ b/example/router_example/lib/ui/bottom_nav/profile/profile_view.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'profile_viewmodel.dart'; + +class ProfileView extends StatelessWidget { + const ProfileView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + builder: (context, viewModel, child) => + const Scaffold(body: Center(child: Text('Profile'))), + viewModelBuilder: () => ProfileViewModel(), + ); + } +} diff --git a/example/router_example/lib/ui/bottom_nav/profile/profile_viewmodel.dart b/example/router_example/lib/ui/bottom_nav/profile/profile_viewmodel.dart new file mode 100644 index 000000000..17596c9ef --- /dev/null +++ b/example/router_example/lib/ui/bottom_nav/profile/profile_viewmodel.dart @@ -0,0 +1,3 @@ +import 'package:stacked/stacked.dart'; + +class ProfileViewModel extends BaseViewModel {} diff --git a/example/router_example/lib/ui/bottomsheets/generic_bottomsheet.dart b/example/router_example/lib/ui/bottomsheets/generic_bottomsheet.dart new file mode 100644 index 000000000..197eeb862 --- /dev/null +++ b/example/router_example/lib/ui/bottomsheets/generic_bottomsheet.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:stacked_services/stacked_services.dart'; + +class GenericBottomSheet extends StatelessWidget { + final SheetRequest request; + final Function(SheetResponse) completer; + + const GenericBottomSheet({ + Key? key, + required this.request, + required this.completer, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.all(25), + padding: const EdgeInsets.all(25), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + request.title ?? 'Generic Bottom Sheet', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.grey[900], + ), + ), + const SizedBox(height: 10), + Text( + request.description ?? '', + style: const TextStyle(color: Colors.grey), + ), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + MaterialButton( + onPressed: () => completer(SheetResponse( + confirmed: true, + data: const GenericBottomSheetResponse( + message: 'SecondaryButton'), + )), + child: Text( + request.secondaryButtonTitle ?? '', + style: TextStyle(color: Theme.of(context).primaryColor), + ), + ), + TextButton( + onPressed: () => completer(SheetResponse( + confirmed: true, + data: const GenericBottomSheetResponse(message: 'MainButton'), + )), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Theme.of(context).primaryColor, + ), + ), + child: Text( + request.mainButtonTitle ?? '', + style: const TextStyle(color: Colors.white), + ), + ) + ], + ) + ], + ), + ); + } +} + +class GenericBottomSheetResponse { + const GenericBottomSheetResponse({ + this.message = 'GenericBottomSheetResponse', + }); + + final String message; +} diff --git a/example/router_example/lib/ui/builder_widget_example/builder_widget_example_view.dart b/example/router_example/lib/ui/builder_widget_example/builder_widget_example_view.dart new file mode 100644 index 000000000..42d9d0721 --- /dev/null +++ b/example/router_example/lib/ui/builder_widget_example/builder_widget_example_view.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:example/ui/home/home_viewmodel.dart'; +import 'package:stacked/stacked.dart'; + +class BuilderWidgetExampleView extends StackedView { + const BuilderWidgetExampleView({Key? key}) : super(key: key); + + @override + Widget builder(BuildContext context, HomeViewModel viewModel, Widget? child) { + return Scaffold( + body: Center( + child: Text(viewModel.title), + ), + floatingActionButton: FloatingActionButton( + onPressed: () => viewModel.updateTitle(), + ), + ); + } + + @override + HomeViewModel viewModelBuilder(BuildContext context) => HomeViewModel(); +} diff --git a/example/router_example/lib/ui/builder_widget_example/builder_widget_example_viewmodel.dart b/example/router_example/lib/ui/builder_widget_example/builder_widget_example_viewmodel.dart new file mode 100644 index 000000000..4d68a9a6f --- /dev/null +++ b/example/router_example/lib/ui/builder_widget_example/builder_widget_example_viewmodel.dart @@ -0,0 +1,12 @@ +import 'package:stacked/stacked.dart'; + +class BuilderWidgetExampleViewModel extends BaseViewModel { + String title = 'default'; + + int counter = 0; + void updateTitle() { + counter++; + title = '$counter'; + notifyListeners(); + } +} diff --git a/example/router_example/lib/ui/dialogs/basic_dialog.dart b/example/router_example/lib/ui/dialogs/basic_dialog.dart new file mode 100644 index 000000000..d8ed24f5c --- /dev/null +++ b/example/router_example/lib/ui/dialogs/basic_dialog.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:stacked_services/stacked_services.dart'; + +class BasicDialog extends StatelessWidget { + final DialogRequest request; + final void Function(DialogResponse) completer; + const BasicDialog({ + Key? key, + required this.request, + required this.completer, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return const SizedBox(); + } +} diff --git a/example/router_example/lib/ui/drop_down_menu_from/select_location_view.dart b/example/router_example/lib/ui/drop_down_menu_from/select_location_view.dart new file mode 100644 index 000000000..0e15e55d9 --- /dev/null +++ b/example/router_example/lib/ui/drop_down_menu_from/select_location_view.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_shared/stacked_shared.dart'; + +import 'select_location_view.form.dart'; +import 'select_location_viewmodel.dart'; + +@FormView(fields: [ + FormDropdownField(name: 'country', items: [ + StaticDropdownItem( + title: 'South Africa', + value: 'ZAR', + ), + StaticDropdownItem( + title: 'United Kingdom', + value: 'UK', + ), + ]), + FormDropdownField(name: 'province', items: [ + StaticDropdownItem( + title: 'Western Cape', + value: 'Western Cape', + ), + StaticDropdownItem( + title: 'Easter Cape', + value: 'Easter Cape', + ), + StaticDropdownItem( + title: 'Free State', + value: 'Free State', + ), + StaticDropdownItem( + title: 'Gauteng', + value: 'Gauteng', + ), + StaticDropdownItem( + title: 'KwaZulu-Natal', + value: 'KwaZulu-Natal', + ), + StaticDropdownItem( + title: 'Limpopo', + value: 'Limpopo', + ), + StaticDropdownItem( + title: 'Mpumalanga', + value: 'Mpumalanga', + ), + StaticDropdownItem( + title: 'Northern Cape', + value: 'Northern Cape', + ), + StaticDropdownItem( + title: 'North West', + value: 'North West', + ), + ]), +]) +class SelectLocationView extends StatelessWidget with $SelectLocationView { + const SelectLocationView({super.key}); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + onViewModelReady: (model) { + syncFormWithViewModel(model); + model.setCountry(CountryValueToTitleMap.keys.first); + model.setProvince(ProvinceValueToTitleMap.keys.first); + }, + viewModelBuilder: () => SelectLocationViewModel(), + builder: (context, model, child) => Scaffold( + backgroundColor: const Color.fromARGB(255, 26, 27, 30), + body: Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + const Text("Select Location", + style: + TextStyle(fontSize: 20.0, fontWeight: FontWeight.w800)), + const Text("Manually select your location"), + const SizedBox(height: 20), + Row( + children: [ + const Text('Country'), + const SizedBox(width: 15), + DropdownButton( + value: model.countryValue, + onChanged: (value) { + model.setCountry(value!); + }, + items: CountryValueToTitleMap.keys + .map( + (value) => DropdownMenuItem( + value: value, + child: Text(CountryValueToTitleMap[value]!), + ), + ) + .toList(), + ), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + const Text('State / Province'), + const SizedBox(width: 15), + DropdownButton( + value: model.provinceValue, + onChanged: (value) { + model.setProvince(value!); + }, + items: ProvinceValueToTitleMap.keys + .map( + (value) => DropdownMenuItem( + value: value, + child: Text(ProvinceValueToTitleMap[value]!), + ), + ) + .toList(), + ), + ], + ), + const SizedBox(height: 10), + ]), + ), + ), + ), + ); + } +} diff --git a/example/router_example/lib/ui/drop_down_menu_from/select_location_view.form.dart b/example/router_example/lib/ui/drop_down_menu_from/select_location_view.form.dart new file mode 100644 index 000000000..261de4d1e --- /dev/null +++ b/example/router_example/lib/ui/drop_down_menu_from/select_location_view.form.dart @@ -0,0 +1,113 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedFormGenerator +// ************************************************************************** + +// ignore_for_file: public_member_api_docs, constant_identifier_names, non_constant_identifier_names,unnecessary_this + +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +const bool _autoTextFieldValidation = true; + +const String CountryValueKey = 'country'; +const String ProvinceValueKey = 'province'; + +final Map CountryValueToTitleMap = { + 'ZAR': 'South Africa', + 'UK': 'United Kingdom', +}; +final Map ProvinceValueToTitleMap = { + 'Western Cape': 'Western Cape', + 'Easter Cape': 'Easter Cape', + 'Free State': 'Free State', + 'Gauteng': 'Gauteng', + 'KwaZulu-Natal': 'KwaZulu-Natal', + 'Limpopo': 'Limpopo', + 'Mpumalanga': 'Mpumalanga', + 'Northern Cape': 'Northern Cape', + 'North West': 'North West', +}; + +mixin $SelectLocationView { + /// Registers a listener on every generated controller that calls [model.setData()] + /// with the latest textController values + void syncFormWithViewModel(FormViewModel model) { + _updateFormData(model, forceValidate: _autoTextFieldValidation); + } + + /// Registers a listener on every generated controller that calls [model.setData()] + /// with the latest textController values + @Deprecated( + 'Use syncFormWithViewModel instead.' + 'This feature was deprecated after 3.1.0.', + ) + void listenToFormUpdated(FormViewModel model) { + _updateFormData(model, forceValidate: _autoTextFieldValidation); + } + + /// Calls dispose on all the generated controllers and focus nodes + void disposeForm() { + // The dispose function for a TextEditingController sets all listeners to null + } +} + +extension ValueProperties on FormViewModel { + bool get hasAnyValidationMessage => this + .fieldsValidationMessages + .values + .any((validation) => validation != null); + + bool get isFormValid { + if (!_autoTextFieldValidation) this.validateForm(); + + return !hasAnyValidationMessage; + } + + String? get countryValue => this.formValueMap[CountryValueKey] as String?; + String? get provinceValue => this.formValueMap[ProvinceValueKey] as String?; + + bool get hasCountry => this.formValueMap.containsKey(CountryValueKey); + bool get hasProvince => this.formValueMap.containsKey(ProvinceValueKey); + + bool get hasCountryValidationMessage => + this.fieldsValidationMessages[CountryValueKey]?.isNotEmpty ?? false; + bool get hasProvinceValidationMessage => + this.fieldsValidationMessages[ProvinceValueKey]?.isNotEmpty ?? false; + + String? get countryValidationMessage => + this.fieldsValidationMessages[CountryValueKey]; + String? get provinceValidationMessage => + this.fieldsValidationMessages[ProvinceValueKey]; +} + +extension Methods on FormViewModel { + void setCountry(String country) { + this.setData( + this.formValueMap..addAll({CountryValueKey: country}), + ); + + if (_autoTextFieldValidation) { + this.validateForm(); + } + } + + void setProvince(String province) { + this.setData( + this.formValueMap..addAll({ProvinceValueKey: province}), + ); + + if (_autoTextFieldValidation) { + this.validateForm(); + } + } + + setCountryValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[CountryValueKey] = validationMessage; + setProvinceValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[ProvinceValueKey] = validationMessage; + + /// Clears text input fields on the Form + void clearForm() {} +} diff --git a/example/router_example/lib/ui/drop_down_menu_from/select_location_viewmodel.dart b/example/router_example/lib/ui/drop_down_menu_from/select_location_viewmodel.dart new file mode 100644 index 000000000..7c91b9f6f --- /dev/null +++ b/example/router_example/lib/ui/drop_down_menu_from/select_location_viewmodel.dart @@ -0,0 +1,10 @@ +import 'package:stacked/stacked.dart'; + +import '../../app/app.logger.dart'; + +class SelectLocationViewModel extends FormViewModel { + var log = getLogger('SelectLocationViewModel'); + + @override + void setFormStatus() {} +} diff --git a/example/router_example/lib/ui/dumb_widgets/description_section.dart b/example/router_example/lib/ui/dumb_widgets/description_section.dart new file mode 100644 index 000000000..daa8cce6a --- /dev/null +++ b/example/router_example/lib/ui/dumb_widgets/description_section.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:example/ui/home/home_viewmodel.dart'; +import 'package:stacked/stacked.dart'; + +class DescriptionSection extends ViewModelWidget { + const DescriptionSection({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, HomeViewModel viewModel) { + return Row( + children: [ + const Text( + 'Description', + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700), + ), + Text(viewModel.title), + ], + ); + } +} diff --git a/example/router_example/lib/ui/dumb_widgets/duplicate_name_widget.dart b/example/router_example/lib/ui/dumb_widgets/duplicate_name_widget.dart new file mode 100644 index 000000000..4f95b4c3d --- /dev/null +++ b/example/router_example/lib/ui/dumb_widgets/duplicate_name_widget.dart @@ -0,0 +1,20 @@ +import 'package:example/datamodels/human.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +class DuplicateNameWidget extends ViewModelWidget { + const DuplicateNameWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, Human viewModel) { + return Row( + children: [ + Text(viewModel.name!), + const SizedBox( + width: 50, + ), + Text(viewModel.name!), + ], + ); + } +} diff --git a/example/router_example/lib/ui/dumb_widgets/full_name_widget.dart b/example/router_example/lib/ui/dumb_widgets/full_name_widget.dart new file mode 100644 index 000000000..8f8c5531d --- /dev/null +++ b/example/router_example/lib/ui/dumb_widgets/full_name_widget.dart @@ -0,0 +1,26 @@ +import 'package:example/datamodels/human.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +class FullNameWidget extends ViewModelWidget { + const FullNameWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, Human viewModel) { + return Row( + children: [ + Text( + viewModel.name!, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 30), + ), + const SizedBox( + width: 50, + ), + Text( + viewModel.surname!, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 30), + ), + ], + ); + } +} diff --git a/example/router_example/lib/ui/dumb_widgets/title_section.dart b/example/router_example/lib/ui/dumb_widgets/title_section.dart new file mode 100644 index 000000000..ed25e07d0 --- /dev/null +++ b/example/router_example/lib/ui/dumb_widgets/title_section.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:example/ui/home/home_viewmodel.dart'; +import 'package:stacked/stacked.dart'; + +class TitleSection extends ViewModelWidget { + const TitleSection({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, HomeViewModel viewModel) { + return Row( + children: [ + const Text( + 'Title', + style: TextStyle(fontSize: 20), + ), + Text(viewModel.title), + ], + ); + } +} diff --git a/example/router_example/lib/ui/form/custom_text_field.dart b/example/router_example/lib/ui/form/custom_text_field.dart new file mode 100644 index 000000000..762c428bb --- /dev/null +++ b/example/router_example/lib/ui/form/custom_text_field.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +class CustomEditingController extends TextEditingController { + static CustomEditingController getCustomEditingController() { + return CustomEditingController(); + } +} diff --git a/example/router_example/lib/ui/form/example_form_view.dart b/example/router_example/lib/ui/form/example_form_view.dart new file mode 100644 index 000000000..11020cd76 --- /dev/null +++ b/example/router_example/lib/ui/form/example_form_view.dart @@ -0,0 +1,191 @@ +import 'package:example/datamodels/clashable_one.dart'; +import 'package:example/ui/form/custom_text_field.dart'; +import 'package:example/ui/form/validators.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_shared/stacked_shared.dart'; + +import 'example_form_view.form.dart'; +import 'example_form_viewmodel.dart'; + +// #1: Add the annotation +@FormView( + fields: [ + FormTextField( + name: 'email', + initialValue: "Lorem", + validator: FormValidators.emailValidator, + ), + FormTextField( + name: 'password', + validator: FormValidators.passwordValidator, + customTextEditingController: + CustomEditingController.getCustomEditingController, + ), + FormTextField( + name: 'shortBio', + customTextEditingController: + CustomEditingController.getCustomEditingController, + ), + FormDateField(name: 'birthDate'), + FormDropdownField( + name: 'doYouLoveFood', + items: [ + StaticDropdownItem( + title: 'Yes', + value: 'YesDr', + ), + StaticDropdownItem( + title: 'No', + value: 'NoDr', + ), + ], + ) + ], + autoTextFieldValidation: false, +) +// #2: with $ExampleFormView +class ExampleFormView extends StatelessWidget with $ExampleFormView { + final Clashable clashableOne; + ExampleFormView({Key? key, required this.clashableOne}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + onViewModelReady: (viewModel) { + // #3: Listen to text updates by calling listenToFormUpdated(model); + syncFormWithViewModel(viewModel); + viewModel.populateForm(); + }, + onDispose: (model) => disposeForm(), + builder: (context, viewModel, child) => Scaffold( + appBar: AppBar( + title: const Text('Example Form View'), + centerTitle: true, + ), + floatingActionButton: FloatingActionButton( + onPressed: viewModel.saveData, + ), + body: SingleChildScrollView( + child: SizedBox( + width: MediaQuery.of(context).size.width, + child: Form( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 300, + ), + child: TextFormField( + //#4: Set email emailController and focus node + controller: emailController, + decoration: const InputDecoration(hintText: 'email'), + keyboardType: TextInputType.emailAddress, + focusNode: emailFocusNode, + ), + ), + Visibility( + visible: viewModel.hasEmailValidationMessage, + child: Text( + viewModel.emailValidationMessage ?? '', + style: const TextStyle(color: Colors.red), + ), + ), + const SizedBox(height: 15), + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 300, + ), + child: TextFormField( + //#5: Set password passwordController and focus node + key: const ValueKey('passwordField'), + controller: passwordController, + decoration: const InputDecoration(hintText: 'password'), + keyboardType: TextInputType.visiblePassword, + obscureText: true, + focusNode: passwordFocusNode, + onFieldSubmitted: (_) => viewModel.saveData(), + ), + ), + Visibility( + visible: viewModel.hasPasswordValidationMessage, + child: Text( + viewModel.passwordValidationMessage ?? '', + style: const TextStyle(color: Colors.red), + ), + ), + const SizedBox(height: 15), + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 300, + ), + child: TextField( + //#6: Set shortBio shortBioController and focus node + maxLines: null, + keyboardType: TextInputType.multiline, + controller: shortBioController, + decoration: const InputDecoration( + hintText: 'Tell us a bit more about yourself', + ), + focusNode: shortBioFocusNode, + ), + ), + const SizedBox(height: 15), + ElevatedButton( + onPressed: () => viewModel.selectBirthDate( + context: context, + firstDate: DateTime.now() + .subtract(const Duration(days: 365 * 5)), + initialDate: DateTime.now(), + lastDate: + DateTime.now().add(const Duration(days: 365 * 5))), + child: Text( + viewModel.hasBirthDate + ? viewModel.birthDateValue.toString() + : 'Select your Date of birth', + ), + ), + const SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Do you love food?'), + const SizedBox(width: 15), + DropdownButton( + key: const ValueKey('dropdownField'), + value: viewModel.doYouLoveFoodValue, + onChanged: (value) { + viewModel.setDoYouLoveFood(value!); + }, + items: DoYouLoveFoodValueToTitleMap.keys + .map( + (value) => DropdownMenuItem( + key: ValueKey('$value key'), + value: value, + child: + Text(DoYouLoveFoodValueToTitleMap[value]!), + ), + ) + .toList(), + ) + ], + ), + const SizedBox(height: 15), + Visibility( + visible: viewModel.showValidationMessage, + child: Text( + viewModel.validationMessage ?? '', + style: const TextStyle(color: Colors.red), + ), + ), + ], + ), + ), + ), + ), + ), + viewModelBuilder: () => ExampleFormViewModel(), + ); + } +} diff --git a/example/router_example/lib/ui/form/example_form_view.form.dart b/example/router_example/lib/ui/form/example_form_view.form.dart new file mode 100644 index 000000000..135e9453d --- /dev/null +++ b/example/router_example/lib/ui/form/example_form_view.form.dart @@ -0,0 +1,327 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// StackedFormGenerator +// ************************************************************************** + +// ignore_for_file: public_member_api_docs, constant_identifier_names, non_constant_identifier_names,unnecessary_this + +import 'package:example/ui/form/custom_text_field.dart'; +import 'package:example/ui/form/validators.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +const bool _autoTextFieldValidation = true; + +const String EmailValueKey = 'email'; +const String PasswordValueKey = 'password'; +const String ShortBioValueKey = 'shortBio'; +const String BirthDateValueKey = 'birthDate'; +const String DoYouLoveFoodValueKey = 'doYouLoveFood'; + +final Map DoYouLoveFoodValueToTitleMap = { + 'YesDr': 'Yes', + 'NoDr': 'No', +}; + +final Map + _ExampleFormViewTextEditingControllers = {}; + +final Map _ExampleFormViewFocusNodes = {}; + +final Map _ExampleFormViewTextValidations = + { + EmailValueKey: FormValidators.emailValidator, + PasswordValueKey: FormValidators.passwordValidator, + ShortBioValueKey: null, +}; + +mixin $ExampleFormView { + TextEditingController get emailController => + _getFormTextEditingController(EmailValueKey, initialValue: 'Lorem'); + CustomEditingController get passwordController => + _getPasswordCustomFormTextEditingController(PasswordValueKey); + CustomEditingController get shortBioController => + _getShortBioCustomFormTextEditingController(ShortBioValueKey); + + FocusNode get emailFocusNode => _getFormFocusNode(EmailValueKey); + FocusNode get passwordFocusNode => _getFormFocusNode(PasswordValueKey); + FocusNode get shortBioFocusNode => _getFormFocusNode(ShortBioValueKey); + + TextEditingController _getFormTextEditingController( + String key, { + String? initialValue, + }) { + if (_ExampleFormViewTextEditingControllers.containsKey(key)) { + return _ExampleFormViewTextEditingControllers[key]!; + } + + _ExampleFormViewTextEditingControllers[key] = + TextEditingController(text: initialValue); + return _ExampleFormViewTextEditingControllers[key]!; + } + + CustomEditingController _getPasswordCustomFormTextEditingController( + String key, + ) { + if (_ExampleFormViewTextEditingControllers.containsKey(key)) { + return _ExampleFormViewTextEditingControllers[key]! + as CustomEditingController; + } + _ExampleFormViewTextEditingControllers[key] = + CustomEditingController.getCustomEditingController(); + return _ExampleFormViewTextEditingControllers[key]! + as CustomEditingController; + } + + CustomEditingController _getShortBioCustomFormTextEditingController( + String key, + ) { + if (_ExampleFormViewTextEditingControllers.containsKey(key)) { + return _ExampleFormViewTextEditingControllers[key]! + as CustomEditingController; + } + _ExampleFormViewTextEditingControllers[key] = + CustomEditingController.getCustomEditingController(); + return _ExampleFormViewTextEditingControllers[key]! + as CustomEditingController; + } + + FocusNode _getFormFocusNode(String key) { + if (_ExampleFormViewFocusNodes.containsKey(key)) { + return _ExampleFormViewFocusNodes[key]!; + } + _ExampleFormViewFocusNodes[key] = FocusNode(); + return _ExampleFormViewFocusNodes[key]!; + } + + /// Registers a listener on every generated controller that calls [model.setData()] + /// with the latest textController values + void syncFormWithViewModel(FormViewModel model) { + emailController.addListener(() => _updateFormData(model)); + passwordController.addListener(() => _updateFormData(model)); + shortBioController.addListener(() => _updateFormData(model)); + + _updateFormData(model, forceValidate: _autoTextFieldValidation); + } + + /// Registers a listener on every generated controller that calls [model.setData()] + /// with the latest textController values + @Deprecated( + 'Use syncFormWithViewModel instead.' + 'This feature was deprecated after 3.1.0.', + ) + void listenToFormUpdated(FormViewModel model) { + emailController.addListener(() => _updateFormData(model)); + passwordController.addListener(() => _updateFormData(model)); + shortBioController.addListener(() => _updateFormData(model)); + + _updateFormData(model, forceValidate: _autoTextFieldValidation); + } + + /// Updates the formData on the FormViewModel + void _updateFormData(FormViewModel model, {bool forceValidate = false}) { + model.setData( + model.formValueMap + ..addAll({ + EmailValueKey: emailController.text, + PasswordValueKey: passwordController.text, + ShortBioValueKey: shortBioController.text, + }), + ); + + if (_autoTextFieldValidation || forceValidate) { + updateValidationData(model); + } + } + + bool validateFormFields(FormViewModel model) { + _updateFormData(model, forceValidate: true); + return model.isFormValid; + } + + /// Calls dispose on all the generated controllers and focus nodes + void disposeForm() { + // The dispose function for a TextEditingController sets all listeners to null + + for (var controller in _ExampleFormViewTextEditingControllers.values) { + controller.dispose(); + } + for (var focusNode in _ExampleFormViewFocusNodes.values) { + focusNode.dispose(); + } + + _ExampleFormViewTextEditingControllers.clear(); + _ExampleFormViewFocusNodes.clear(); + } +} + +extension ValueProperties on FormViewModel { + bool get hasAnyValidationMessage => this + .fieldsValidationMessages + .values + .any((validation) => validation != null); + + bool get isFormValid { + if (!_autoTextFieldValidation) this.validateForm(); + + return !hasAnyValidationMessage; + } + + String? get emailValue => this.formValueMap[EmailValueKey] as String?; + String? get passwordValue => this.formValueMap[PasswordValueKey] as String?; + String? get shortBioValue => this.formValueMap[ShortBioValueKey] as String?; + DateTime? get birthDateValue => + this.formValueMap[BirthDateValueKey] as DateTime?; + String? get doYouLoveFoodValue => + this.formValueMap[DoYouLoveFoodValueKey] as String?; + + set emailValue(String? value) { + this.setData( + this.formValueMap..addAll({EmailValueKey: value}), + ); + + if (_ExampleFormViewTextEditingControllers.containsKey(EmailValueKey)) { + _ExampleFormViewTextEditingControllers[EmailValueKey]?.text = value ?? ''; + } + } + + set passwordValue(String? value) { + this.setData( + this.formValueMap..addAll({PasswordValueKey: value}), + ); + + if (_ExampleFormViewTextEditingControllers.containsKey(PasswordValueKey)) { + _ExampleFormViewTextEditingControllers[PasswordValueKey]?.text = + value ?? ''; + } + } + + set shortBioValue(String? value) { + this.setData( + this.formValueMap..addAll({ShortBioValueKey: value}), + ); + + if (_ExampleFormViewTextEditingControllers.containsKey(ShortBioValueKey)) { + _ExampleFormViewTextEditingControllers[ShortBioValueKey]?.text = + value ?? ''; + } + } + + bool get hasEmail => + this.formValueMap.containsKey(EmailValueKey) && + (emailValue?.isNotEmpty ?? false); + bool get hasPassword => + this.formValueMap.containsKey(PasswordValueKey) && + (passwordValue?.isNotEmpty ?? false); + bool get hasShortBio => + this.formValueMap.containsKey(ShortBioValueKey) && + (shortBioValue?.isNotEmpty ?? false); + bool get hasBirthDate => this.formValueMap.containsKey(BirthDateValueKey); + bool get hasDoYouLoveFood => + this.formValueMap.containsKey(DoYouLoveFoodValueKey); + + bool get hasEmailValidationMessage => + this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false; + bool get hasPasswordValidationMessage => + this.fieldsValidationMessages[PasswordValueKey]?.isNotEmpty ?? false; + bool get hasShortBioValidationMessage => + this.fieldsValidationMessages[ShortBioValueKey]?.isNotEmpty ?? false; + bool get hasBirthDateValidationMessage => + this.fieldsValidationMessages[BirthDateValueKey]?.isNotEmpty ?? false; + bool get hasDoYouLoveFoodValidationMessage => + this.fieldsValidationMessages[DoYouLoveFoodValueKey]?.isNotEmpty ?? false; + + String? get emailValidationMessage => + this.fieldsValidationMessages[EmailValueKey]; + String? get passwordValidationMessage => + this.fieldsValidationMessages[PasswordValueKey]; + String? get shortBioValidationMessage => + this.fieldsValidationMessages[ShortBioValueKey]; + String? get birthDateValidationMessage => + this.fieldsValidationMessages[BirthDateValueKey]; + String? get doYouLoveFoodValidationMessage => + this.fieldsValidationMessages[DoYouLoveFoodValueKey]; +} + +extension Methods on FormViewModel { + Future selectBirthDate({ + required BuildContext context, + required DateTime initialDate, + required DateTime firstDate, + required DateTime lastDate, + }) async { + final selectedDate = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + ); + + if (selectedDate != null) { + this.setData( + this.formValueMap..addAll({BirthDateValueKey: selectedDate}), + ); + } + + if (_autoTextFieldValidation) { + this.validateForm(); + } + } + + void setDoYouLoveFood(String doYouLoveFood) { + this.setData( + this.formValueMap..addAll({DoYouLoveFoodValueKey: doYouLoveFood}), + ); + + if (_autoTextFieldValidation) { + this.validateForm(); + } + } + + setEmailValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[EmailValueKey] = validationMessage; + setPasswordValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[PasswordValueKey] = validationMessage; + setShortBioValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[ShortBioValueKey] = validationMessage; + setBirthDateValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[BirthDateValueKey] = validationMessage; + setDoYouLoveFoodValidationMessage(String? validationMessage) => + this.fieldsValidationMessages[DoYouLoveFoodValueKey] = validationMessage; + + /// Clears text input fields on the Form + void clearForm() { + emailValue = ''; + passwordValue = ''; + shortBioValue = ''; + } + + /// Validates text input fields on the Form + void validateForm() { + this.setValidationMessages({ + EmailValueKey: getValidationMessage(EmailValueKey), + PasswordValueKey: getValidationMessage(PasswordValueKey), + ShortBioValueKey: getValidationMessage(ShortBioValueKey), + }); + } +} + +/// Returns the validation message for the given key +String? getValidationMessage(String key) { + final validatorForKey = _ExampleFormViewTextValidations[key]; + if (validatorForKey == null) return null; + + String? validationMessageForKey = validatorForKey( + _ExampleFormViewTextEditingControllers[key]!.text, + ); + + return validationMessageForKey; +} + +/// Updates the fieldsValidationMessages on the FormViewModel +void updateValidationData(FormViewModel model) => model.setValidationMessages({ + EmailValueKey: getValidationMessage(EmailValueKey), + PasswordValueKey: getValidationMessage(PasswordValueKey), + ShortBioValueKey: getValidationMessage(ShortBioValueKey), + }); diff --git a/example/router_example/lib/ui/form/example_form_viewmodel.dart b/example/router_example/lib/ui/form/example_form_viewmodel.dart new file mode 100644 index 000000000..6d434a7ef --- /dev/null +++ b/example/router_example/lib/ui/form/example_form_viewmodel.dart @@ -0,0 +1,46 @@ +import 'package:example/app/app.locator.dart'; +import 'package:example/app/app.logger.dart'; +import 'package:example/app/app.router.dart'; +import 'package:example/services/shared_preferences_service.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; + +import 'example_form_view.form.dart'; + +// #5: extend from FormViewModel +class ExampleFormViewModel extends FormViewModel { + final log = getLogger('FormViewModel'); + final _routerService = exampleLocator(); + final _preferences = exampleLocator(); + + ExampleFormViewModel() { + log.wtf('hash:${_preferences.hashCode}'); + } + + void populateForm() { + DoYouLoveFoodValueToTitleMap.addAll({'MaybeDr': 'Maybe'}); + setDoYouLoveFood(DoYouLoveFoodValueToTitleMap.keys.first); + } + + @override + void setFormStatus() { + log.i('Set form Status with data: $formValueMap'); + + // Set a validation message for the entire form + if (hasPasswordValidationMessage) { + setValidationMessage('Error in the form, please check again'); + } + } + + // If the dev doesn't want realtime validation then they can + // simply validate in the function that they'll use to submit the + // data to the backend or db. + + Future saveData() async { + if (!isFormValid) return; + + // here we can run custom functionality to save to our api + + _routerService.replaceWith(const BottomNavExampleRoute()); + } +} diff --git a/example/router_example/lib/ui/form/validators.dart b/example/router_example/lib/ui/form/validators.dart new file mode 100644 index 000000000..daa9e043f --- /dev/null +++ b/example/router_example/lib/ui/form/validators.dart @@ -0,0 +1,25 @@ +class FormValidators { + static String? passwordValidator(String? value) { + if (value == null || value.isEmpty) { + return "Password should not be empty"; + } + + if (value.length < 6) { + return "Password must has at least 6 alpha characters"; + } + + return null; + } + + static String? emailValidator(String? value) { + if (value == null || value.isEmpty) { + return 'Email is required'; + } + + if (!value.contains('@')) { + return 'Email is not valid'; + } + + return null; + } +} diff --git a/example/router_example/lib/ui/future_example_view/future_example_view.dart b/example/router_example/lib/ui/future_example_view/future_example_view.dart new file mode 100644 index 000000000..6eee7e6e7 --- /dev/null +++ b/example/router_example/lib/ui/future_example_view/future_example_view.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'future_example_viewmodel.dart'; + +class FutureExampleView extends StatelessWidget { + const FutureExampleView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + builder: (context, viewModel, child) => Scaffold( + body: viewModel.hasError + ? Container( + color: Colors.red, + alignment: Alignment.center, + child: const Text( + 'An error has occered while running the future', + style: TextStyle(color: Colors.white), + ), + ) + : Center( + child: viewModel.isBusy + ? const CircularProgressIndicator() + : Text(viewModel.data!), + ), + ), + viewModelBuilder: () => FutureExampleViewModel(), + ); + } +} diff --git a/example/router_example/lib/ui/future_example_view/future_example_viewmodel.dart b/example/router_example/lib/ui/future_example_view/future_example_viewmodel.dart new file mode 100644 index 000000000..49521d0a0 --- /dev/null +++ b/example/router_example/lib/ui/future_example_view/future_example_viewmodel.dart @@ -0,0 +1,15 @@ +import 'package:stacked/stacked.dart'; + +class FutureExampleViewModel extends FutureViewModel { + Future getDataFromServer() async { + await Future.delayed(const Duration(seconds: 3)); + // throw Exception('This is an error'); // Uncomment to trigger error UI + return 'This is fetched from everywhere'; + } + + @override + void onError(error) {} + + @override + Future futureToRun() => getDataFromServer(); +} diff --git a/example/router_example/lib/ui/home/home_view.dart b/example/router_example/lib/ui/home/home_view.dart new file mode 100644 index 000000000..a00182bdc --- /dev/null +++ b/example/router_example/lib/ui/home/home_view.dart @@ -0,0 +1,86 @@ +import 'package:example/datamodels/clashable_one.dart'; +import 'package:example/datamodels/home_type.dart'; +import 'package:example/ui/smart_widgets/widget_one/widget_one.dart'; +import 'package:example/ui/smart_widgets/widget_two/widget_two.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'home_viewmodel.dart'; + +class HomeView extends StatelessWidget { + final String? title; + final bool? isLoggedIn; + final List homeTypes; + final Clashable Function(String name)? clashableGetter; + + const HomeView({ + Key? key, + this.title = 'hello', + this.isLoggedIn = false, + this.clashableGetter, + this.homeTypes = const [HomeType.apartment, HomeType.house], + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + viewModelBuilder: () => HomeViewModel(), + onViewModelReady: (viewModel) => viewModel.initialise(), + builder: (context, viewModel, child) => Scaffold( + body: Center( + child: Padding( + padding: const EdgeInsets.fromLTRB(12, 20, 12, 40), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Expanded(child: SizedBox.shrink()), + Text(viewModel.title), + if (viewModel.hasMessage) Text(viewModel.modelMessage!), + const SizedBox(height: 20), + const Row( + mainAxisSize: MainAxisSize.min, + children: [ + WidgetOne(), + SizedBox(width: 50), + WidgetTwo(id: 2), + ], + ), + const SizedBox(height: 20), + if (title != null) Text(title!), + const SizedBox(height: 20), + MaterialButton( + onPressed: viewModel.isUserLoggedIn + ? viewModel.signOutUser + : viewModel.signInUser, + color: Colors.blue.shade400, + height: 60, + minWidth: 150, + child: viewModel.isBusy + ? const CircularProgressIndicator( + color: Colors.white, + ) + : Text( + viewModel.isUserLoggedIn ? 'Sign Out' : 'Sign In', + style: const TextStyle(color: Colors.white), + ), + ), + const Expanded(child: SizedBox.shrink()), + Align( + alignment: Alignment.centerLeft, + child: Text('Operative System: ${viewModel.operativeSystem}'), + ), + Align( + alignment: Alignment.centerLeft, + child: Text('Device ID: ${viewModel.deviceId}'), + ), + ], + ), + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: viewModel.navigate, + ), + ), + ); + } +} diff --git a/example/router_example/lib/ui/home/home_view_multiple_widgets.dart b/example/router_example/lib/ui/home/home_view_multiple_widgets.dart new file mode 100644 index 000000000..53bdfb25f --- /dev/null +++ b/example/router_example/lib/ui/home/home_view_multiple_widgets.dart @@ -0,0 +1,32 @@ +import 'package:example/ui/dumb_widgets/description_section.dart'; +import 'package:example/ui/dumb_widgets/title_section.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'home_viewmodel.dart'; + +class HomeViewMultipleWidgets extends StatelessWidget { + const HomeViewMultipleWidgets({super.key}); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + viewModelBuilder: () => HomeViewModel(), + onViewModelReady: (viewModel) => viewModel.initialise(), + builder: (context, viewModel, _) => Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () { + viewModel.updateTitle(); + }, + ), + body: const Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TitleSection(), + DescriptionSection(), + ], + ), + ), + ); + } +} diff --git a/example/router_example/lib/ui/home/home_view_traditional.dart b/example/router_example/lib/ui/home/home_view_traditional.dart new file mode 100644 index 000000000..07953d8ab --- /dev/null +++ b/example/router_example/lib/ui/home/home_view_traditional.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'home_viewmodel.dart'; + +class HomeViewTraditional extends StatelessWidget { + const HomeViewTraditional({super.key}); + + @override + Widget build(BuildContext context) { + // Using the withConsumer constructor gives you the traditional viewmodel + // binding which will rebuild when notifyListeners is called. This is used + // when the model does not have to be consumed by multiple different UI's. + return ViewModelBuilder.reactive( + viewModelBuilder: () => HomeViewModel(), + onViewModelReady: (viewModel) => viewModel.initialise(), + builder: (context, viewModel, child) => Scaffold( + floatingActionButton: const UpdateTitleButton(), + body: Center( + child: Text(viewModel.title), + ), + ), + ); + } +} + +class UpdateTitleButton extends ViewModelWidget { + const UpdateTitleButton({ + Key? key, + }) : super(key: key, reactive: false); + + @override + Widget build(BuildContext context, viewModel) { + return FloatingActionButton( + onPressed: () { + viewModel.updateTitle(); + }, + ); + } +} diff --git a/example/router_example/lib/ui/home/home_viewmodel.dart b/example/router_example/lib/ui/home/home_viewmodel.dart new file mode 100644 index 000000000..41dbbdd7b --- /dev/null +++ b/example/router_example/lib/ui/home/home_viewmodel.dart @@ -0,0 +1,62 @@ +import 'package:example/app/app.locator.dart'; +import 'package:example/app/app.logger.dart'; +import 'package:example/app/app.router.dart'; +import 'package:example/services/native_actions_service.dart'; +import 'package:example/services/shared_preferences_service.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; + +class HomeViewModel extends BaseViewModel with MessageStateHelper { + final log = getLogger('HomeViewModel'); + final _routerService = exampleLocator(); + final _preferences = exampleLocator(); + final _nativeActionsService = exampleLocator(); + + String title = 'default'; + int counter = 0; + + String get deviceId => _nativeActionsService.deviceId; + String get operativeSystem => _nativeActionsService.operativeSystem; + + bool get isUserLoggedIn => _preferences.isUserLoggedIn; + + void navigate() { + _routerService.navigateTo(const NonReactiveViewRoute()); + } + + void initialise() { + setMessage('initialise'); + log.i('initialise'); + title = 'Initialised'; + notifyListeners(); + } + + void updateTitle() { + counter++; + title = '$counter'; + notifyListeners(); + } + + void updateData() { + notifyListeners(); + } + + void updateTile(String value) { + title = value; + notifyListeners(); + } + + Future signInUser() async { + setBusy(true); + await Future.delayed(const Duration(milliseconds: 500)); + _preferences.isUserLoggedIn = true; + setBusy(false); + } + + Future signOutUser() async { + setBusy(true); + await Future.delayed(const Duration(milliseconds: 500)); + _preferences.isUserLoggedIn = false; + setBusy(false); + } +} diff --git a/example/router_example/lib/ui/multiple_futures_example/multiple_futures_example_view.dart b/example/router_example/lib/ui/multiple_futures_example/multiple_futures_example_view.dart new file mode 100644 index 000000000..bc4cb10b6 --- /dev/null +++ b/example/router_example/lib/ui/multiple_futures_example/multiple_futures_example_view.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:example/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart'; +import 'package:stacked/stacked.dart'; + +class MultipleFuturesExampleView extends StatelessWidget { + const MultipleFuturesExampleView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + builder: (context, viewModel, child) => Scaffold( + body: Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 50, + height: 50, + alignment: Alignment.center, + color: Colors.yellow, + child: viewModel.fetchingNumber + ? const CircularProgressIndicator() + : Text(viewModel.fetchedNumber.toString()), + ), + const SizedBox( + width: 20, + ), + Container( + width: 50, + height: 50, + alignment: Alignment.center, + color: Colors.red, + child: viewModel.fetchingString + ? const CircularProgressIndicator() + : Text(viewModel.fetchedString), + ), + ], + ), + ), + ), + viewModelBuilder: () => MultipleFuturesExampleViewModel()); + } +} diff --git a/example/router_example/lib/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart b/example/router_example/lib/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart new file mode 100644 index 000000000..5a9b47613 --- /dev/null +++ b/example/router_example/lib/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart @@ -0,0 +1,28 @@ +import 'package:stacked/stacked.dart'; + +const String _numberDelayFuture = 'delayedNumber'; +const String _stringDelayFuture = 'delayedString'; + +class MultipleFuturesExampleViewModel extends MultipleFutureViewModel { + int get fetchedNumber => dataMap![_numberDelayFuture]; + String get fetchedString => dataMap![_stringDelayFuture]; + + bool get fetchingNumber => busy(_numberDelayFuture); + bool get fetchingString => busy(_stringDelayFuture); + + @override + Map get futuresMap => { + _numberDelayFuture: getNumberAfterDelay, + _stringDelayFuture: getStringAfterDelay, + }; + + Future getNumberAfterDelay() async { + await Future.delayed(const Duration(seconds: 2)); + return 3; + } + + Future getStringAfterDelay() async { + await Future.delayed(const Duration(seconds: 3)); + return 'String data'; + } +} diff --git a/example/router_example/lib/ui/multiple_streams_example/multiple_streams_example_view.dart b/example/router_example/lib/ui/multiple_streams_example/multiple_streams_example_view.dart new file mode 100644 index 000000000..fdd7f9d3f --- /dev/null +++ b/example/router_example/lib/ui/multiple_streams_example/multiple_streams_example_view.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'multiple_streams_example_viewmodel.dart'; + +class MultipleStreamsExampleView extends StatelessWidget { + const MultipleStreamsExampleView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + builder: (context, viewModel, child) => Scaffold( + body: Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 50, + height: 50, + alignment: Alignment.center, + color: Colors.yellow, + child: !viewModel.hasNumberData + ? const CircularProgressIndicator() + : Column( + children: [ + Text('${viewModel.stringStreamDelay}ms'), + Text(viewModel.number.toString()), + ], + ), + ), + const SizedBox( + width: 20, + ), + Container( + width: 50, + height: 90, + alignment: Alignment.center, + color: Colors.red, + child: !viewModel.hasRandomString + ? const CircularProgressIndicator() + : Column( + children: [ + Text('${viewModel.numbersStreamDelay}ms'), + Text(viewModel.randomString), + ], + ), + ), + ], + ), + ), + floatingActionButton: MaterialButton( + onPressed: viewModel.swapStreams, + child: const Text('Change Stream Source Faster'), + ), + ), + viewModelBuilder: () => MultipleStreamsExampleViewModel()); + } +} diff --git a/example/router_example/lib/ui/multiple_streams_example/multiple_streams_example_viewmodel.dart b/example/router_example/lib/ui/multiple_streams_example/multiple_streams_example_viewmodel.dart new file mode 100644 index 000000000..a9eee4fca --- /dev/null +++ b/example/router_example/lib/ui/multiple_streams_example/multiple_streams_example_viewmodel.dart @@ -0,0 +1,50 @@ +import 'dart:math'; + +import 'package:stacked/stacked.dart'; + +const String _numbersStreamKey = 'numbers-stream'; +const String _stringStreamKey = 'string-stream'; + +class MultipleStreamsExampleViewModel extends MultipleStreamViewModel { + int get number => dataMap![_numbersStreamKey]; + bool get hasNumberData => dataReady(_numbersStreamKey); + + String get randomString => dataMap![_stringStreamKey]; + bool get hasRandomString => dataReady(_stringStreamKey); + + Stream numbersStream([int delay = 500]) async* { + var random = Random(); + while (true) { + await Future.delayed(Duration(milliseconds: delay)); + yield random.nextInt(999); + } + } + + Stream stringStream([int delay = 2000]) async* { + var random = Random(); + while (true) { + await Future.delayed(Duration(milliseconds: delay)); + var randomLength = random.nextInt(50); + var randomString = ''; + for (var i = 0; i < randomLength; i++) { + randomString += String.fromCharCode(random.nextInt(50)); + } + yield randomString; + } + } + + int numbersStreamDelay = 500; + int stringStreamDelay = 2000; + + @override + Map get streamsMap => { + _numbersStreamKey: StreamData(numbersStream(numbersStreamDelay)), + _stringStreamKey: StreamData(stringStream(stringStreamDelay)), + }; + + void swapStreams() { + numbersStreamDelay -= 100; + stringStreamDelay -= 500; + notifySourceChanged(); + } +} diff --git a/example/router_example/lib/ui/nonreactive/nonreactive_view.dart b/example/router_example/lib/ui/nonreactive/nonreactive_view.dart new file mode 100644 index 000000000..4298913b0 --- /dev/null +++ b/example/router_example/lib/ui/nonreactive/nonreactive_view.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'nonreactive_viewmodel.dart'; + +class NonReactiveView extends StatelessWidget { + const NonReactiveView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.nonReactive( + builder: (context, viewModel, child) => Scaffold( + appBar: AppBar( + title: const Text('Non Reactive View'), + centerTitle: true, + leading: IconButton( + onPressed: () { + viewModel.navigateBackHome(); + }, + icon: const Icon(Icons.arrow_back_ios)), + ), + floatingActionButton: FloatingActionButton( + onPressed: viewModel.updateTitle, + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: viewModel.navigateToNewView, + child: const Text('Go to stream counter view'), + ), + const SizedBox(height: 10), + Text(viewModel.title), + ], + ), + ), + ), + viewModelBuilder: () => NonReactiveViewModel(), + ); + } +} diff --git a/example/router_example/lib/ui/nonreactive/nonreactive_viewmodel.dart b/example/router_example/lib/ui/nonreactive/nonreactive_viewmodel.dart new file mode 100644 index 000000000..0b987fe25 --- /dev/null +++ b/example/router_example/lib/ui/nonreactive/nonreactive_viewmodel.dart @@ -0,0 +1,26 @@ +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; + +import '../../app/app.locator.dart'; +import '../../app/app.router.dart'; +import '../../datamodels/clashable_two.dart'; + +class NonReactiveViewModel extends BaseViewModel { + final _routerService = exampleLocator(); + String title = 'This should not change'; + + void updateTitle() { + title += '. This has changed'; + notifyListeners(); + } + + void navigateToNewView() { + _routerService.navigateTo( + StreamCounterViewRoute(clashableTwo: const [Clashable(22)]), + ); + } + + void navigateBackHome() { + _routerService.clearStackAndShow(HomeViewRoute(title: 'Home')); + } +} diff --git a/example/router_example/lib/ui/smart_widgets/widget_one/widget_one.dart b/example/router_example/lib/ui/smart_widgets/widget_one/widget_one.dart new file mode 100644 index 000000000..bd73b96ab --- /dev/null +++ b/example/router_example/lib/ui/smart_widgets/widget_one/widget_one.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'widget_one_viewmodel.dart'; + +class WidgetOne extends StatelessWidget { + const WidgetOne({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + viewModelBuilder: () => WidgetOneViewModel(), + builder: (context, viewModel, child) => GestureDetector( + onTap: () => viewModel.updatePostCount(), + child: Container( + width: 100, + height: 100, + color: Colors.green, + alignment: Alignment.center, + child: !viewModel.busy(viewModel) + ? Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Tap to increment', + style: TextStyle(fontSize: 10), + ), + Text( + viewModel.postCount.toString(), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 40, + ), + ), + ], + ) + : const Center( + child: CircularProgressIndicator(), + ), + ), + ), + ); + } +} diff --git a/example/router_example/lib/ui/smart_widgets/widget_one/widget_one_viewmodel.dart b/example/router_example/lib/ui/smart_widgets/widget_one/widget_one_viewmodel.dart new file mode 100644 index 000000000..eae4c942a --- /dev/null +++ b/example/router_example/lib/ui/smart_widgets/widget_one/widget_one_viewmodel.dart @@ -0,0 +1,24 @@ +import 'package:example/app/app.locator.dart'; +import 'package:example/services/information_service.dart'; +import 'package:stacked/stacked.dart'; + +class WidgetOneViewModel extends ReactiveViewModel { + final InformationService _informationService = + exampleLocator(); + int get postCount => _informationService.postCount; + + void updatePostCount() { + _informationService.updatePostCount(); + } + + Future longUpdateStuff() async { + await runBusyFuture(updateStuff()); + } + + Future updateStuff() { + return Future.delayed(const Duration(seconds: 3)); + } + + @override + List get listenableServices => [_informationService]; +} diff --git a/example/router_example/lib/ui/smart_widgets/widget_two/widget_two.dart b/example/router_example/lib/ui/smart_widgets/widget_two/widget_two.dart new file mode 100644 index 000000000..bf72ebcd5 --- /dev/null +++ b/example/router_example/lib/ui/smart_widgets/widget_two/widget_two.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import 'widget_two_viewmodel.dart'; + +class WidgetTwo extends StatelessWidget { + final int id; + const WidgetTwo({ + Key? key, + required this.id, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + viewModelBuilder: () => WidgetTwoViewModel(id), + builder: (context, viewModel, child) => GestureDetector( + onTap: () => viewModel.reset(), + child: Container( + width: 100, + height: 100, + color: Colors.red[((id % 10) * 100)], + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Tap to Reset', + style: TextStyle(fontSize: 10), + ), + Text( + viewModel.postCount.toString(), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 40, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/example/router_example/lib/ui/smart_widgets/widget_two/widget_two_viewmodel.dart b/example/router_example/lib/ui/smart_widgets/widget_two/widget_two_viewmodel.dart new file mode 100644 index 000000000..cfe489404 --- /dev/null +++ b/example/router_example/lib/ui/smart_widgets/widget_two/widget_two_viewmodel.dart @@ -0,0 +1,18 @@ +import 'package:example/app/app.locator.dart'; +import 'package:example/services/information_service.dart'; +import 'package:stacked/stacked.dart'; + +class WidgetTwoViewModel extends ReactiveViewModel { + final _informationService = exampleLocator(); + int get postCount => _informationService.postCount; + + final int id; + WidgetTwoViewModel(this.id); + + void reset() { + _informationService.resetCount(); + } + + @override + List get listenableServices => [_informationService]; +} diff --git a/example/router_example/lib/ui/startup/startup_view.dart b/example/router_example/lib/ui/startup/startup_view.dart new file mode 100644 index 000000000..78df9fbe6 --- /dev/null +++ b/example/router_example/lib/ui/startup/startup_view.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:example/ui/startup/startup_viewmodel.dart'; +import 'package:stacked/stacked.dart'; + +class StartupView extends StackedView { + const StartupView({super.key}); + + @override + Widget builder( + BuildContext context, + StartupVieWModel viewModel, + Widget? child, + ) { + return const Scaffold(body: Center(child: Text('Startup View'))); + } + + @override + StartupVieWModel viewModelBuilder(BuildContext context) { + return StartupVieWModel(); + } + + @override + void onViewModelReady(StartupVieWModel viewModel) { + viewModel.initialise(); + super.onViewModelReady(viewModel); + } +} diff --git a/example/router_example/lib/ui/startup/startup_viewmodel.dart b/example/router_example/lib/ui/startup/startup_viewmodel.dart new file mode 100644 index 000000000..2050ca6c5 --- /dev/null +++ b/example/router_example/lib/ui/startup/startup_viewmodel.dart @@ -0,0 +1,13 @@ +import 'package:example/app/app.locator.dart'; +import 'package:example/app/app.logger.dart'; +import 'package:example/services/shared_preferences_service.dart'; +import 'package:stacked/stacked.dart'; + +class StartupVieWModel extends BaseViewModel { + final _log = getLogger('StartupVieWModel'); + final _preferences = exampleLocator(); + + void initialise() { + _log.d('isUserLoggedIn:${_preferences.isUserLoggedIn}'); + } +} diff --git a/example/router_example/lib/ui/stream_view/stream_counter_view.dart b/example/router_example/lib/ui/stream_view/stream_counter_view.dart new file mode 100644 index 000000000..22d9cc389 --- /dev/null +++ b/example/router_example/lib/ui/stream_view/stream_counter_view.dart @@ -0,0 +1,47 @@ +import 'package:example/datamodels/clashable_two.dart'; +import 'package:flutter/material.dart'; +import 'package:example/ui/stream_view/stream_counter_viewmodel.dart'; +import 'package:stacked/stacked.dart'; + +class StreamCounterView extends StatelessWidget { + final List clashableTwo; + const StreamCounterView({Key? key, required this.clashableTwo}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + builder: (context, viewModel, child) => Scaffold( + appBar: AppBar( + title: const Text('Stream Counter View'), + centerTitle: true, + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: viewModel.changeStreamSources, + child: const Text('Change Stream Sources'), + ), + const SizedBox(height: 10), + Text( + viewModel.title, + textAlign: TextAlign.center, + ), + const SizedBox(height: 20), + Text( + viewModel.streamSource, + textAlign: TextAlign.center, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: viewModel.navigateToNewView, + ), + ), + viewModelBuilder: () => StreamCounterViewModel(), + ); + } +} diff --git a/example/router_example/lib/ui/stream_view/stream_counter_viewmodel.dart b/example/router_example/lib/ui/stream_view/stream_counter_viewmodel.dart new file mode 100644 index 000000000..6c88f1f7e --- /dev/null +++ b/example/router_example/lib/ui/stream_view/stream_counter_viewmodel.dart @@ -0,0 +1,54 @@ +import 'package:example/app/app.locator.dart'; +import 'package:example/datamodels/clashable_one.dart'; +import 'package:example/services/epoch_service.dart'; +import 'package:example/ui/form/example_form_view.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; + +class StreamCounterViewModel extends StreamViewModel { + final _routerService = exampleLocator(); + + String get title => 'This is the time since epoch in seconds \n $data'; + + late Stream _currentSource; + bool isSlowEpochNumbers = true; + + String get streamSource => isSlowEpochNumbers ? 'Slow' : 'Fast'; + + StreamCounterViewModel() { + _setSource(); + } + + void _setSource() { + _currentSource = isSlowEpochNumbers + ? exampleLocator().epochUpdatesNumbers() + : exampleLocator().epochUpdateNumbersQuickly(); + } + + @override + Stream get stream => _currentSource; + + @override + void onData(int? data) {} + + @override + void onCancel() {} + + @override + void onSubscribed() {} + + @override + void onError(error) {} + + void changeStreamSources() { + isSlowEpochNumbers = !isSlowEpochNumbers; + _setSource(); + notifySourceChanged(); + } + + void navigateToNewView() { + _routerService.navigateWithTransition( + ExampleFormView(clashableOne: const Clashable('one')), + transitionBuilder: TransitionsBuilders.zoomIn); + } +} diff --git a/example/router_example/linux/.gitignore b/example/router_example/linux/.gitignore new file mode 100644 index 000000000..d3896c984 --- /dev/null +++ b/example/router_example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/example/router_example/linux/CMakeLists.txt b/example/router_example/linux/CMakeLists.txt new file mode 100644 index 000000000..537c01df8 --- /dev/null +++ b/example/router_example/linux/CMakeLists.txt @@ -0,0 +1,139 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "router_example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.router_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/example/router_example/linux/flutter/CMakeLists.txt b/example/router_example/linux/flutter/CMakeLists.txt new file mode 100644 index 000000000..d5bd01648 --- /dev/null +++ b/example/router_example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/example/router_example/linux/flutter/generated_plugin_registrant.cc b/example/router_example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..e71a16d23 --- /dev/null +++ b/example/router_example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/example/router_example/linux/flutter/generated_plugin_registrant.h b/example/router_example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..e0f0a47bc --- /dev/null +++ b/example/router_example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/example/router_example/linux/flutter/generated_plugins.cmake b/example/router_example/linux/flutter/generated_plugins.cmake new file mode 100644 index 000000000..2e1de87a7 --- /dev/null +++ b/example/router_example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/example/router_example/linux/main.cc b/example/router_example/linux/main.cc new file mode 100644 index 000000000..e7c5c5437 --- /dev/null +++ b/example/router_example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/example/router_example/linux/my_application.cc b/example/router_example/linux/my_application.cc new file mode 100644 index 000000000..a3e7601f8 --- /dev/null +++ b/example/router_example/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "router_example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "router_example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/example/router_example/linux/my_application.h b/example/router_example/linux/my_application.h new file mode 100644 index 000000000..72271d5e4 --- /dev/null +++ b/example/router_example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/example/router_example/macos/.gitignore b/example/router_example/macos/.gitignore new file mode 100644 index 000000000..746adbb6b --- /dev/null +++ b/example/router_example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/example/router_example/macos/Flutter/Flutter-Debug.xcconfig b/example/router_example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 000000000..4b81f9b2d --- /dev/null +++ b/example/router_example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/router_example/macos/Flutter/Flutter-Release.xcconfig b/example/router_example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 000000000..5caa9d157 --- /dev/null +++ b/example/router_example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/router_example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/router_example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 000000000..2e86147d3 --- /dev/null +++ b/example/router_example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import device_info_plus +import firebase_core +import firebase_crashlytics +import package_info_plus +import shared_preferences_foundation + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin")) + FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) +} diff --git a/example/router_example/macos/Podfile b/example/router_example/macos/Podfile new file mode 100644 index 000000000..c795730db --- /dev/null +++ b/example/router_example/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/example/router_example/macos/Runner.xcodeproj/project.pbxproj b/example/router_example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..a8fa5c6b5 --- /dev/null +++ b/example/router_example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,791 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 9DBE342E71AF4E7D92549B69 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7EC8559288BA671F5518475F /* Pods_Runner.framework */; }; + AF9DD8025BB10D5F1B7DB376 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B063068E3271A32BE419EE7 /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1B063068E3271A32BE419EE7 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2B2833D4B9FB13FE2F4EC768 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* router_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = router_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 49422E6C576A5779EF923EA4 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 7EC8559288BA671F5518475F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 890E9F10CC8EDB95A0143F32 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + AB33D5693CFF485BA6D7819A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + B129D638BF6A605DEC041538 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + B8B76C87EB6791F9C3EEA2E4 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AF9DD8025BB10D5F1B7DB376 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9DBE342E71AF4E7D92549B69 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 9AF08D7C6AB8ED5CDC431716 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* router_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 9AF08D7C6AB8ED5CDC431716 /* Pods */ = { + isa = PBXGroup; + children = ( + AB33D5693CFF485BA6D7819A /* Pods-Runner.debug.xcconfig */, + 49422E6C576A5779EF923EA4 /* Pods-Runner.release.xcconfig */, + B129D638BF6A605DEC041538 /* Pods-Runner.profile.xcconfig */, + 2B2833D4B9FB13FE2F4EC768 /* Pods-RunnerTests.debug.xcconfig */, + 890E9F10CC8EDB95A0143F32 /* Pods-RunnerTests.release.xcconfig */, + B8B76C87EB6791F9C3EEA2E4 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 7EC8559288BA671F5518475F /* Pods_Runner.framework */, + 1B063068E3271A32BE419EE7 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + A7F23567B41C7D4073E39594 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 067BFE1FACA3F734C8FF4D9E /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + DDEAEB93F3F86BB93E7E5EFC /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* router_example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 067BFE1FACA3F734C8FF4D9E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + A7F23567B41C7D4073E39594 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + DDEAEB93F3F86BB93E7E5EFC /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2B2833D4B9FB13FE2F4EC768 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.routerExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/router_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/router_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 890E9F10CC8EDB95A0143F32 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.routerExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/router_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/router_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B8B76C87EB6791F9C3EEA2E4 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.routerExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/router_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/router_example"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/stacked_themes/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/router_example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 96% rename from packages/stacked_themes/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to example/router_example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index fc6bf8074..18d981003 100644 --- a/packages/stacked_themes/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/example/router_example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -1,8 +1,8 @@ - - - - - IDEDidComputeMac32BitWarning - - - + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/router_example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 74% rename from packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to example/router_example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfd..dfee07e72 100644 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/router_example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ @@ -27,19 +27,28 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + + + + + - - diff --git a/packages/stacked_hooks/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/router_example/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 67% rename from packages/stacked_hooks/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to example/router_example/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16e..21a3cc14c 100644 --- a/packages/stacked_hooks/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/router_example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/packages/stacked_themes/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/router_example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 96% rename from packages/stacked_themes/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to example/router_example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index fc6bf8074..18d981003 100644 --- a/packages/stacked_themes/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/example/router_example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -1,8 +1,8 @@ - - - - - IDEDidComputeMac32BitWarning - - - + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/router_example/macos/Runner/AppDelegate.swift b/example/router_example/macos/Runner/AppDelegate.swift new file mode 100644 index 000000000..d53ef6437 --- /dev/null +++ b/example/router_example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..a2ec33f19 --- /dev/null +++ b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 000000000..82b6f9d9a Binary files /dev/null and b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 000000000..13b35eba5 Binary files /dev/null and b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 000000000..0a3f5fa40 Binary files /dev/null and b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 000000000..bdb57226d Binary files /dev/null and b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 000000000..f083318e0 Binary files /dev/null and b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 000000000..326c0e72c Binary files /dev/null and b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 000000000..2f1632cfd Binary files /dev/null and b/example/router_example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/example/router_example/macos/Runner/Base.lproj/MainMenu.xib b/example/router_example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 000000000..80e867a4e --- /dev/null +++ b/example/router_example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/router_example/macos/Runner/Configs/AppInfo.xcconfig b/example/router_example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 000000000..ad5b4a44a --- /dev/null +++ b/example/router_example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = router_example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.routerExample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright Β© 2023 com.example. All rights reserved. diff --git a/example/router_example/macos/Runner/Configs/Debug.xcconfig b/example/router_example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 000000000..36b0fd946 --- /dev/null +++ b/example/router_example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/example/router_example/macos/Runner/Configs/Release.xcconfig b/example/router_example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 000000000..dff4f4956 --- /dev/null +++ b/example/router_example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/example/router_example/macos/Runner/Configs/Warnings.xcconfig b/example/router_example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 000000000..42bcbf478 --- /dev/null +++ b/example/router_example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/stacked_themes/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/router_example/macos/Runner/DebugProfile.entitlements similarity index 54% rename from packages/stacked_themes/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to example/router_example/macos/Runner/DebugProfile.entitlements index af0309c4d..dddb8a30c 100644 --- a/packages/stacked_themes/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/example/router_example/macos/Runner/DebugProfile.entitlements @@ -1,8 +1,12 @@ - - - - - PreviewsEnabled - - - + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/example/router_example/macos/Runner/Info.plist b/example/router_example/macos/Runner/Info.plist new file mode 100644 index 000000000..4789daa6a --- /dev/null +++ b/example/router_example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/example/router_example/macos/Runner/MainFlutterWindow.swift b/example/router_example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 000000000..3cc05eb23 --- /dev/null +++ b/example/router_example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/stacked_themes/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/router_example/macos/Runner/Release.entitlements similarity index 78% rename from packages/stacked_themes/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to example/router_example/macos/Runner/Release.entitlements index af0309c4d..852fa1a47 100644 --- a/packages/stacked_themes/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/example/router_example/macos/Runner/Release.entitlements @@ -1,8 +1,8 @@ - - - - - PreviewsEnabled - - - + + + + + com.apple.security.app-sandbox + + + diff --git a/example/router_example/macos/RunnerTests/RunnerTests.swift b/example/router_example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..5418c9f53 --- /dev/null +++ b/example/router_example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/packages/stacked_services/example/pubspec.yaml b/example/router_example/pubspec.yaml similarity index 76% rename from packages/stacked_services/example/pubspec.yaml rename to example/router_example/pubspec.yaml index a7978ba19..5ffc30b66 100644 --- a/packages/stacked_services/example/pubspec.yaml +++ b/example/router_example/pubspec.yaml @@ -1,5 +1,6 @@ name: example description: A new Flutter project. +publish_to: none # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -14,7 +15,7 @@ description: A new Flutter project. version: 1.0.0+1 environment: - sdk: ">=2.2.0 <3.0.0" + sdk: ">=2.17.5 <3.0.0" dependencies: flutter: @@ -22,20 +23,52 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.2 - get_it: - injectable: - auto_route: ^0.6.9 + cupertino_icons: ^1.0.2 + # state management + stacked: stacked_services: - path: ../ + stacked_hooks: + stacked_themes: + stacked_crashlytics: + stacked_shared: + + flutter_hooks: ^0.18.4 + # navigation + get: ^4.6.3 + animations: ^2.0.3 + url_strategy: ^0.2.0 + shared_preferences: ^2.1.1 + device_info_plus: ^9.0.2 + package_info_plus: ^4.0.2 + +dependency_overrides: + stacked: + path: ../../ + stacked_services: + path: ../../../services/ + stacked_hooks: + path: ../../../hooks/ + stacked_themes: + path: ../../../themes/ + stacked_crashlytics: + path: ../../../crashlytics/ + stacked_generator: + path: ../../../generator/ + stacked_shared: + path: ../../../core/ dev_dependencies: flutter_test: sdk: flutter - injectable_generator: - auto_route_generator: + integration_test: + sdk: flutter + + # dependency injection build_runner: + stacked_generator: + + flutter_lints: # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/example/router_example/test/stacked_example_test.dart b/example/router_example/test/stacked_example_test.dart new file mode 100644 index 000000000..978d00e35 --- /dev/null +++ b/example/router_example/test/stacked_example_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('StackedExampleTest -', () { + group('test -', () { + test('just a place holder', () { + expect(true, isTrue); + }); + }); + }); +} diff --git a/example/router_example/test_driver/integration_test.dart b/example/router_example/test_driver/integration_test.dart new file mode 100644 index 000000000..b38629cca --- /dev/null +++ b/example/router_example/test_driver/integration_test.dart @@ -0,0 +1,3 @@ +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/packages/stacked_services/example/web/favicon.png b/example/router_example/web/favicon.png similarity index 100% rename from packages/stacked_services/example/web/favicon.png rename to example/router_example/web/favicon.png diff --git a/packages/stacked_services/example/web/icons/Icon-192.png b/example/router_example/web/icons/Icon-192.png similarity index 100% rename from packages/stacked_services/example/web/icons/Icon-192.png rename to example/router_example/web/icons/Icon-192.png diff --git a/packages/stacked_services/example/web/icons/Icon-512.png b/example/router_example/web/icons/Icon-512.png similarity index 100% rename from packages/stacked_services/example/web/icons/Icon-512.png rename to example/router_example/web/icons/Icon-512.png diff --git a/example/router_example/web/icons/Icon-maskable-192.png b/example/router_example/web/icons/Icon-maskable-192.png new file mode 100644 index 000000000..eb9b4d76e Binary files /dev/null and b/example/router_example/web/icons/Icon-maskable-192.png differ diff --git a/example/router_example/web/icons/Icon-maskable-512.png b/example/router_example/web/icons/Icon-maskable-512.png new file mode 100644 index 000000000..d69c56691 Binary files /dev/null and b/example/router_example/web/icons/Icon-maskable-512.png differ diff --git a/example/router_example/web/index.html b/example/router_example/web/index.html new file mode 100644 index 000000000..1941b3bf0 --- /dev/null +++ b/example/router_example/web/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + router_example + + + + + + + + + + diff --git a/packages/stacked_services/example/web/manifest.json b/example/router_example/web/manifest.json similarity index 56% rename from packages/stacked_services/example/web/manifest.json rename to example/router_example/web/manifest.json index 8c012917d..40da4c7e0 100644 --- a/packages/stacked_services/example/web/manifest.json +++ b/example/router_example/web/manifest.json @@ -1,6 +1,6 @@ { - "name": "example", - "short_name": "example", + "name": "router_example", + "short_name": "router_example", "start_url": ".", "display": "standalone", "background_color": "#0175C2", @@ -18,6 +18,18 @@ "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" } ] } diff --git a/example/router_example/windows/.gitignore b/example/router_example/windows/.gitignore new file mode 100644 index 000000000..d492d0d98 --- /dev/null +++ b/example/router_example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/example/router_example/windows/CMakeLists.txt b/example/router_example/windows/CMakeLists.txt new file mode 100644 index 000000000..520d39cc1 --- /dev/null +++ b/example/router_example/windows/CMakeLists.txt @@ -0,0 +1,102 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(router_example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "router_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/example/router_example/windows/flutter/CMakeLists.txt b/example/router_example/windows/flutter/CMakeLists.txt new file mode 100644 index 000000000..930d2071a --- /dev/null +++ b/example/router_example/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/example/router_example/windows/flutter/generated_plugin_registrant.cc b/example/router_example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..8b6d4680a --- /dev/null +++ b/example/router_example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/example/router_example/windows/flutter/generated_plugin_registrant.h b/example/router_example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..dc139d85a --- /dev/null +++ b/example/router_example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/example/router_example/windows/flutter/generated_plugins.cmake b/example/router_example/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000..b93c4c30c --- /dev/null +++ b/example/router_example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/example/router_example/windows/runner/CMakeLists.txt b/example/router_example/windows/runner/CMakeLists.txt new file mode 100644 index 000000000..394917c05 --- /dev/null +++ b/example/router_example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/example/router_example/windows/runner/Runner.rc b/example/router_example/windows/runner/Runner.rc new file mode 100644 index 000000000..da6a81a8d --- /dev/null +++ b/example/router_example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "router_example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "router_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "router_example.exe" "\0" + VALUE "ProductName", "router_example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/example/router_example/windows/runner/flutter_window.cpp b/example/router_example/windows/runner/flutter_window.cpp new file mode 100644 index 000000000..b25e363ef --- /dev/null +++ b/example/router_example/windows/runner/flutter_window.cpp @@ -0,0 +1,66 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/example/router_example/windows/runner/flutter_window.h b/example/router_example/windows/runner/flutter_window.h new file mode 100644 index 000000000..6da0652f0 --- /dev/null +++ b/example/router_example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/example/router_example/windows/runner/main.cpp b/example/router_example/windows/runner/main.cpp new file mode 100644 index 000000000..091c35b0b --- /dev/null +++ b/example/router_example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"router_example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/example/router_example/windows/runner/resource.h b/example/router_example/windows/runner/resource.h new file mode 100644 index 000000000..66a65d1e4 --- /dev/null +++ b/example/router_example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/example/router_example/windows/runner/resources/app_icon.ico b/example/router_example/windows/runner/resources/app_icon.ico new file mode 100644 index 000000000..c04e20caf Binary files /dev/null and b/example/router_example/windows/runner/resources/app_icon.ico differ diff --git a/example/router_example/windows/runner/runner.exe.manifest b/example/router_example/windows/runner/runner.exe.manifest new file mode 100644 index 000000000..a42ea7687 --- /dev/null +++ b/example/router_example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/example/router_example/windows/runner/utils.cpp b/example/router_example/windows/runner/utils.cpp new file mode 100644 index 000000000..b2b08734d --- /dev/null +++ b/example/router_example/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/example/router_example/windows/runner/utils.h b/example/router_example/windows/runner/utils.h new file mode 100644 index 000000000..3879d5475 --- /dev/null +++ b/example/router_example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/example/router_example/windows/runner/win32_window.cpp b/example/router_example/windows/runner/win32_window.cpp new file mode 100644 index 000000000..60608d0fe --- /dev/null +++ b/example/router_example/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/example/router_example/windows/runner/win32_window.h b/example/router_example/windows/runner/win32_window.h new file mode 100644 index 000000000..e901dde68 --- /dev/null +++ b/example/router_example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/stacked/lib/src/code_generation/router/extended_navigator.dart b/lib/src/code_generation/router_annotation/extended_navigator.dart similarity index 72% rename from packages/stacked/lib/src/code_generation/router/extended_navigator.dart rename to lib/src/code_generation/router_annotation/extended_navigator.dart index f130c30ea..37f33b042 100644 --- a/packages/stacked/lib/src/code_generation/router/extended_navigator.dart +++ b/lib/src/code_generation/router_annotation/extended_navigator.dart @@ -1,13 +1,14 @@ import 'dart:async'; +import 'dart:ui'; -import 'package:stacked/src/code_generation/router/route_data.dart'; -import 'package:stacked/src/code_generation/router/route_guard.dart'; -import 'package:stacked/src/code_generation/router/router_base.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; -import 'package:stacked/src/code_generation/router/router_utils.dart'; +import 'package:stacked_shared/stacked_shared.dart'; +import 'route_data.dart'; +import 'router_base.dart'; +import 'router_utils.dart'; import 'uri_extension.dart'; RectTween _createRectTween(Rect? begin, Rect? end) { @@ -22,8 +23,6 @@ extension BuildContextX on BuildContext { ExtendedNavigator.of(this, rootRouter: true, nullOk: true); } -typedef OnNavigationRejected = void Function(RouteGuard? guard); - class ExtendedNavigator extends StatefulWidget { static TransitionBuilder builder({ GlobalKey? navigatorKey, @@ -32,7 +31,6 @@ class ExtendedNavigator extends StatefulWidget { String? initialRoute, Object? initialRouteArgs, required T router, - List guards = const [], String? name, TransitionBuilder? builder, }) => @@ -45,7 +43,6 @@ class ExtendedNavigator extends StatefulWidget { initialRoute: initialRoute, initialRouteArgs: initialRouteArgs, router: router, - guards: guards, name: name, ); if (builder != null) { @@ -54,9 +51,8 @@ class ExtendedNavigator extends StatefulWidget { return extendedNav; }; - ExtendedNavigator({ + const ExtendedNavigator({ this.router, - this.guards = const [], this.name, this.initialRoute, this.onUnknownRoute, @@ -72,7 +68,6 @@ class ExtendedNavigator extends StatefulWidget { final String? initialRoute; final Object? initialRouteArgs; final T? router; - final List guards; final String? name; @override @@ -80,7 +75,7 @@ class ExtendedNavigator extends StatefulWidget { static get _placeHolderRoute => PageRouteBuilder( pageBuilder: (context, __, ___) => Container( - color: Theme.of(context).backgroundColor, + color: Theme.of(context).colorScheme.background, ), ); @@ -100,12 +95,7 @@ class ExtendedNavigator extends StatefulWidget { var rootRoute = navigator.widget.onGenerateRoute!(RouteSettings(name: root)); if (rootRoute != null) { - if ((rootRoute.settings as RouteData).routeMatch.hasGuards) { - initialRoutes.add(_placeHolderRoute); - router._guardedInitialRoutes.add(rootRoute); - } else { - initialRoutes.add(rootRoute); - } + initialRoutes.add(rootRoute); } } @@ -114,9 +104,7 @@ class ExtendedNavigator extends StatefulWidget { var route = navigator.widget.onGenerateRoute!(settings); if (route != null) { - var data = route.settings as RouteData; - if (router._guardedInitialRoutes.isNotEmpty || - data.routeMatch.hasGuards) { + if (router._guardedInitialRoutes.isNotEmpty) { router._guardedInitialRoutes.add(route); if (initialRoutes.isEmpty) { initialRoutes.add(_placeHolderRoute); @@ -138,7 +126,6 @@ class ExtendedNavigator extends StatefulWidget { ExtendedNavigator call(_, navigator) => this; static ExtendedNavigatorState? named(String name) { - assert(name != null); return _NavigatorsContainer._instance.get(name); } @@ -168,9 +155,8 @@ class ExtendedNavigator extends StatefulWidget { class ExtendedNavigatorState extends State> with WidgetsBindingObserver { - final _registeredGuards = {}; T? router; - List _guardedInitialRoutes = []; + final List _guardedInitialRoutes = []; ExtendedNavigatorState? get parent => _parent; @@ -193,11 +179,11 @@ class ExtendedNavigatorState @override void initState() { super.initState(); - WidgetsBinding.instance!.addObserver(this); + ambiguate(WidgetsBinding.instance)!.addObserver(this); _navigatorKey = widget.navigatorKey ?? GlobalKey(); _heroController = HeroController(createRectTween: _createRectTween); if (_guardedInitialRoutes.isNotEmpty) { - SchedulerBinding.instance!.addPostFrameCallback((_) { + ambiguate(SchedulerBinding.instance)!.addPostFrameCallback((_) { _pushAllGuarded(_guardedInitialRoutes); }); } @@ -205,16 +191,13 @@ class ExtendedNavigatorState Future _pushAllGuarded(Iterable routes) async { for (var route in routes) { - var data = (route.settings as RouteData); - if (await _canNavigate(data.template)) { - if (data.template == Navigator.defaultRouteName) { - _navigator! - .pushAndRemoveUntil(route, RouteData.withPath(data.template)); - } else { - _navigator!.push(route); - } + var data = (route.settings as RouteDataV1); + + if (data.template == Navigator.defaultRouteName) { + _navigator! + .pushAndRemoveUntil(route, RouteDataV1.withPath(data.template)); } else { - break; + _navigator!.push(route); } } } @@ -240,7 +223,7 @@ class ExtendedNavigatorState router = widget.router; var initial = widget.initialRoute; var initialRouteArgs = widget.initialRouteArgs; - var basePath; + String? basePath; var parentData = ParentRouteData.of(context); if (router == null && parentData != null) { router = parentData.router as T?; @@ -253,22 +236,22 @@ class ExtendedNavigatorState assert(router != null); return Navigator( key: _navigatorKey, - initialRoute: WidgetsBinding.instance!.window.defaultRouteName != + initialRoute: PlatformDispatcher.instance.defaultRouteName != Navigator.defaultRouteName - ? WidgetsBinding.instance!.window.defaultRouteName - : initial ?? WidgetsBinding.instance!.window.defaultRouteName, + ? PlatformDispatcher.instance.defaultRouteName + : initial ?? PlatformDispatcher.instance.defaultRouteName, observers: List.from(widget.observers)..add(_heroController!), onGenerateRoute: (RouteSettings settings) { return router!.onGenerateRoute(settings, basePath); }, onUnknownRoute: widget.onUnknownRoute ?? defaultUnknownRoutePage, onGenerateInitialRoutes: (NavigatorState navigator, String initialRoute) { - var initialUri; + Uri initialUri; if (parentData != null) { if (parentData.initialRoute!.hasEmptyPath) { initialUri = parentData.initialRoute!.replace(path: initialRoute); } else { - initialUri = parentData.initialRoute; + initialUri = parentData.initialRoute!; } } else { initialUri = Uri.parse(initialRoute); @@ -289,11 +272,7 @@ class ExtendedNavigatorState _parent = context.findAncestorStateOfType(); if (_parent != null) { _parent!.children.add(this); - if (_parent!._registeredGuards != null) { - _registeredGuards.addAll(_parent!._registeredGuards); - } } - widget.guards.forEach(_registerGuard); if (routerName != null) { _NavigatorsContainer._instance.register(this, name: routerName); @@ -306,23 +285,14 @@ class ExtendedNavigatorState } } - void _registerGuard(RouteGuard guard) { - _registeredGuards[guard.runtimeType] = guard; - } - @optionalTypeArgs - Future? push(String routeName, - {Object? arguments, - Map? queryParams, - OnNavigationRejected? onReject}) async { - return await _canNavigate( - routeName, - arguments: arguments, - onReject: onReject, - ) - ? _navigator!.pushNamed(_buildPath(routeName, queryParams), - arguments: arguments) - : null; + Future? push( + String routeName, { + Object? arguments, + Map? queryParams, + }) async { + return _navigator! + .pushNamed(_buildPath(routeName, queryParams), arguments: arguments); } String _buildPath(String routeName, Map? queryParams) { @@ -341,15 +311,11 @@ class ExtendedNavigatorState TO? result, Object? arguments, Map? queryParams, - OnNavigationRejected? onReject, }) async { - return await _canNavigate(routeName, - arguments: arguments, onReject: onReject) - ? _navigator!.pushReplacementNamed( - _buildPath(routeName, queryParams), - arguments: arguments, - result: result) - : null; + return _navigator!.pushReplacementNamed( + _buildPath(routeName, queryParams), + arguments: arguments, + result: result); } @optionalTypeArgs @@ -358,16 +324,12 @@ class ExtendedNavigatorState RoutePredicate predicate, { Object? arguments, Map? queryParams, - OnNavigationRejected? onReject, }) async { - return await _canNavigate(newRouteName, - arguments: arguments, onReject: onReject) - ? _navigator!.pushNamedAndRemoveUntil( - _buildPath(newRouteName, queryParams), - predicate, - arguments: arguments, - ) - : null; + return _navigator!.pushNamedAndRemoveUntil( + _buildPath(newRouteName, queryParams), + predicate, + arguments: arguments, + ); } @optionalTypeArgs @@ -376,14 +338,12 @@ class ExtendedNavigatorState String anchorPath, { Object? arguments, Map? queryParams, - OnNavigationRejected? onReject, }) { return pushAndRemoveUntil( newRouteName, - RouteData.withPath(anchorPath), + RouteDataV1.withPath(anchorPath), arguments: arguments, queryParams: queryParams, - onReject: onReject, ); } @@ -393,15 +353,17 @@ class ExtendedNavigatorState TO? result, Object? arguments, Map? queryParams, - OnNavigationRejected? onReject, }) { pop(result); - return push(routeName, - arguments: arguments, queryParams: queryParams, onReject: onReject); + return push( + routeName, + arguments: arguments, + queryParams: queryParams, + ); } void popUntilPath(String path) { - popUntil(RouteData.withPath(path)); + popUntil(RouteDataV1.withPath(path)); } void popUntil(RoutePredicate predicate) { @@ -425,35 +387,6 @@ class ExtendedNavigatorState return _navigator!.canPop(); } - Future canNavigate(String routeName) => _canNavigate(routeName); - - Future _canNavigate(String routeName, - {Object? arguments, OnNavigationRejected? onReject}) async { - var match = router!.findMatch(RouteSettings(name: routeName)); - if (match == null || !match.hasGuards) { - return true; - } - - for (Type guardType in match.routeDef.guards!) { - if (!await _getGuard(guardType)! - .canNavigate(this, routeName, arguments)) { - if (onReject != null) { - onReject(_getGuard(guardType)); - } - return false; - } - } - return true; - } - - RouteGuard? _getGuard(Type guardType) { - if (_registeredGuards[guardType] == null) { - throw ('$guardType is not registered!' - '\nYou have to add your guards to ExtendedNavigator widget'); - } - return _registeredGuards[guardType]; - } - @override void deactivate() { if (_willPopCallback != null) { @@ -467,7 +400,7 @@ class ExtendedNavigatorState if (_parent != null) { _parent!.children.remove(this); } - WidgetsBinding.instance!.removeObserver(this); + ambiguate(WidgetsBinding.instance)!.removeObserver(this); if (routerName != null) { _NavigatorsContainer._instance.remove(routerName); diff --git a/lib/src/code_generation/router_annotation/parameters.dart b/lib/src/code_generation/router_annotation/parameters.dart new file mode 100644 index 000000000..c96cce43a --- /dev/null +++ b/lib/src/code_generation/router_annotation/parameters.dart @@ -0,0 +1,129 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/cupertino.dart'; + +class Parameters { + final Map _params; + + const Parameters(Map? params) : _params = params ?? const {}; + + Map get rawMap => _params; + + @override + String toString() { + return _params.toString(); + } + + Parameters operator +(Parameters other) => + Parameters({..._params, ...other._params}); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Parameters && + runtimeType == other.runtimeType && + const MapEquality( + values: DeepCollectionEquality(), + ).equals(_params, other._params); + + @override + int get hashCode => const MapEquality().hash(_params); + + bool get isNotEmpty => _params.isNotEmpty; + bool get isEmpty => _params.isEmpty; + + dynamic get(String key, [defaultValue]) { + return _params[key] ?? defaultValue; + } + + String? optString(String key, [String? defaultValue]) => + _params[key] ?? defaultValue; + + String getString(String key, [String? defaultValue]) { + var val = _params[key] ?? defaultValue; + if (val == null) { + throw FlutterError( + 'Failed to parse [String] $key value from ${_params[key]}'); + } + return val; + } + + int? optInt(String key, [int? defaultValue]) { + var param = _params[key]; + if (param == null) { + return defaultValue; + } else if (param is int) { + return param; + } else { + return int.tryParse(param.toString()) ?? defaultValue; + } + } + + int getInt(String key, [int? defaultValue]) { + var val = optInt(key, defaultValue); + if (val == null) { + throw FlutterError( + 'Failed to parse [int] $key value from ${_params[key]}'); + } + return val; + } + + double? optDouble(String key, [double? defaultValue]) { + var param = _params[key]; + if (param == null) { + return defaultValue; + } else if (param is double) { + return param; + } else { + return double.tryParse(param.toString()) ?? defaultValue; + } + } + + double getDouble(String key, [double? defaultValue]) { + var val = optDouble(key, defaultValue); + if (val == null) { + throw FlutterError( + 'Failed to parse [double] $key value from ${_params[key]}'); + } + return val; + } + + num? optNum(String key, [num? defaultValue]) { + var param = _params[key]; + if (param == null) { + return defaultValue; + } else if (param is num) { + return param; + } else { + return double.tryParse(param.toString()) ?? defaultValue; + } + } + + num getNum(String key, [num? defaultValue]) { + var val = optNum(key, defaultValue); + if (val == null) { + throw FlutterError( + 'Failed to parse [num] $key value from ${_params[key]}'); + } + return val; + } + + bool? optBool(String key, [bool? defaultValue]) { + switch (_params[key]?.toLowerCase()) { + case 'true': + return true; + case 'false': + return false; + default: + return defaultValue; + } + } + + bool getBool(String key, [bool? defaultValue]) { + var val = optBool(key, defaultValue); + if (val == null) { + throw FlutterError( + 'Failed to parse [bool] $key value from ${_params[key]}'); + } + return val; + } +} diff --git a/packages/stacked/lib/src/code_generation/router/route_data.dart b/lib/src/code_generation/router_annotation/route_data.dart similarity index 67% rename from packages/stacked/lib/src/code_generation/router/route_data.dart rename to lib/src/code_generation/router_annotation/route_data.dart index 3332d6634..9d7c1f724 100644 --- a/packages/stacked/lib/src/code_generation/router/route_data.dart +++ b/lib/src/code_generation/router_annotation/route_data.dart @@ -1,21 +1,32 @@ -import 'package:stacked/src/code_generation/router/parameters.dart'; -import 'package:stacked/src/code_generation/router/route_matcher.dart'; -import 'package:stacked/src/code_generation/router/router_base.dart'; import 'package:flutter/widgets.dart'; +import 'parameters.dart'; +import 'route_matcher.dart'; +import 'router_base.dart'; + @immutable -class RouteData extends RouteSettings { - RouteData(this.routeMatch) +class RouteDataV1 extends RouteSettings { + RouteDataV1(this.routeMatch) : _pathParams = routeMatch.pathParams, _queryParams = routeMatch.queryParams, fragment = routeMatch.uri.fragment, - super(name: routeMatch.name, arguments: routeMatch.arguments); + transition = routeMatch.arguments is Map + ? ((routeMatch.arguments as Map?)?['transition']) + : null, + super( + name: routeMatch.name, + arguments: routeMatch.arguments is Map + ? ((routeMatch.arguments as Map?)?['arguments']) + : routeMatch.arguments, + ); - final RouteMatch routeMatch; + final RouteMatchV1 routeMatch; final Parameters _pathParams; final Parameters _queryParams; final String fragment; + final RouteTransitionsBuilder? transition; + String get template => routeMatch.template; Parameters get queryParams => _queryParams; @@ -51,10 +62,10 @@ class RouteData extends RouteSettings { 'path: ${routeMatch.path}, fullName: ${routeMatch.name}, args: $arguments, params: $_pathParams, query: $_queryParams}'; } - static RouteData? of(BuildContext context) { + static RouteDataV1? of(BuildContext context) { var modal = ModalRoute.of(context); - if (modal != null && modal.settings is RouteData) { - return modal.settings as RouteData; + if (modal != null && modal.settings is RouteDataV1) { + return modal.settings as RouteDataV1; } else { return null; } @@ -64,21 +75,21 @@ class RouteData extends RouteSettings { return (Route route) { return !route.willHandlePopInternally && route is ModalRoute && - route.settings is RouteData && - (route.settings as RouteData).template == path; + route.settings is RouteDataV1 && + (route.settings as RouteDataV1).template == path; }; } } @immutable -class ParentRouteData extends RouteData { +class ParentRouteData extends RouteDataV1 { final Uri? initialRoute; final T? router; ParentRouteData({ this.initialRoute, this.router, - required RouteMatch matchResult, + required RouteMatchV1 matchResult, }) : super(matchResult); Object? get initialRouteArgs => _initialArgsToPass; diff --git a/packages/stacked/lib/src/code_generation/router/route_def.dart b/lib/src/code_generation/router_annotation/route_def.dart similarity index 82% rename from packages/stacked/lib/src/code_generation/router/route_def.dart rename to lib/src/code_generation/router_annotation/route_def.dart index 83c55bdb6..575196ee3 100644 --- a/packages/stacked/lib/src/code_generation/router/route_def.dart +++ b/lib/src/code_generation/router_annotation/route_def.dart @@ -1,8 +1,7 @@ -import 'package:stacked/src/code_generation/router/router_base.dart'; +import 'router_base.dart'; class RouteDef { final String template; - final List? guards; final RouterBase? generator; final Pattern pattern; final Type? page; @@ -10,7 +9,6 @@ class RouteDef { RouteDef( this.template, { this.page, - this.guards, this.generator, }) : pattern = _buildPathPattern(template); diff --git a/packages/stacked/lib/src/code_generation/router/route_matcher.dart b/lib/src/code_generation/router_annotation/route_matcher.dart similarity index 79% rename from packages/stacked/lib/src/code_generation/router/route_matcher.dart rename to lib/src/code_generation/router_annotation/route_matcher.dart index f7c6d51c7..874a6175e 100644 --- a/packages/stacked/lib/src/code_generation/router/route_matcher.dart +++ b/lib/src/code_generation/router_annotation/route_matcher.dart @@ -1,22 +1,22 @@ -import 'package:stacked/src/code_generation/router/parameters.dart'; -import 'package:stacked/src/code_generation/router/route_def.dart'; - import 'package:flutter/widgets.dart'; +import 'parameters.dart'; +import 'route_def.dart'; import 'uri_extension.dart'; class RouteMatcher { final Uri _uri; final RouteSettings? _settings; - RouteMatcher(RouteSettings this._settings) : _uri = Uri.parse(_settings.name!); + RouteMatcher(RouteSettings this._settings) + : _uri = Uri.parse(_settings.name!); RouteMatcher.fromUri(this._uri) : _settings = null; - RouteMatch? match(RouteDef route, {bool fullMatch = false}) { + RouteMatchV1? match(RouteDef route, {bool fullMatch = false}) { var pattern = fullMatch ? '${route.pattern}\$' : route.pattern; var match = RegExp(pattern as String).stringMatch(_uri.path); - RouteMatch? matchResult; + RouteMatchV1? matchResult; if (match != null) { // strip trailing forward slash if (match.endsWith("/") && match.length > 1) { @@ -40,13 +40,13 @@ class RouteMatcher { // passing args to the last destination // when pushing deep links var args = _settings?.arguments; - var argsToPass; + Object? argsToPass; if (!rest.hasEmptyPath) { argsToPass = args; args = null; } - matchResult = RouteMatch( + matchResult = RouteMatchV1( name: !rest.hasEmptyPath || !segment.hasQueryParams || route.isParent ? segment.path : segment.toString(), @@ -73,14 +73,14 @@ class RouteMatcher { } @immutable -class RouteMatch extends RouteSettings { +class RouteMatchV1 extends RouteSettings { final Uri uri; final RouteDef routeDef; final Uri rest; final Map pathParamsMap; final Object? initialArgsToPass; - RouteMatch({ + const RouteMatchV1({ required this.uri, required this.routeDef, required this.rest, @@ -92,8 +92,6 @@ class RouteMatch extends RouteSettings { bool get hasRest => !rest.hasEmptyPath; - bool get hasGuards => routeDef.guards?.isNotEmpty == true; - bool get isParent => routeDef.generator != null; String get template => routeDef.template; @@ -104,18 +102,17 @@ class RouteMatch extends RouteSettings { Parameters get pathParams => Parameters(pathParamsMap); - @override RouteSettings copyWith({ String? name, Object? arguments, }) { - return RouteMatch( + return RouteMatchV1( name: name ?? this.name, arguments: arguments ?? this.arguments, - initialArgsToPass: this.initialArgsToPass, - uri: this.uri, - routeDef: this.routeDef, - rest: this.rest, - pathParamsMap: this.pathParamsMap); + initialArgsToPass: initialArgsToPass, + uri: uri, + routeDef: routeDef, + rest: rest, + pathParamsMap: pathParamsMap); } } diff --git a/packages/stacked/lib/src/code_generation/router/router_base.dart b/lib/src/code_generation/router_annotation/router_base.dart similarity index 78% rename from packages/stacked/lib/src/code_generation/router/router_base.dart rename to lib/src/code_generation/router_annotation/router_base.dart index 1423ce343..d40b9bcf8 100644 --- a/packages/stacked/lib/src/code_generation/router/router_base.dart +++ b/lib/src/code_generation/router_annotation/router_base.dart @@ -1,10 +1,10 @@ -import 'package:stacked/src/code_generation/router/route_data.dart'; -import 'package:stacked/src/code_generation/router/route_def.dart'; +import 'route_data.dart'; +import 'route_def.dart'; import 'package:flutter/widgets.dart'; -import 'package:stacked/src/code_generation/router/route_matcher.dart'; +import 'route_matcher.dart'; -typedef StackedRouteFactory = Route Function(RouteData data); +typedef StackedRouteFactory = Route Function(RouteDataV1 data); typedef RouterBuilder = T Function(); abstract class RouterBase { @@ -15,15 +15,14 @@ abstract class RouterBase { Set get allRoutes => routes.map((e) => e.template).toSet(); Route? onGenerateRoute(RouteSettings settings, [String? basePath]) { - assert(routes != null); - assert(settings != null); var match = findMatch(settings); if (match != null) { if (basePath != null) { - match = match.copyWith(name: _joinPath(basePath, match.name)) as RouteMatch?; + match = match.copyWith(name: _joinPath(basePath, match.name)) + as RouteMatchV1?; } - RouteData data; + RouteDataV1 data; if (match!.isParent) { data = ParentRouteData( matchResult: match, @@ -31,7 +30,7 @@ abstract class RouterBase { router: match.routeDef.generator, ); } else { - data = RouteData(match); + data = RouteDataV1(match); } return pagesMap[match.routeDef.page!]!(data); } @@ -39,7 +38,7 @@ abstract class RouterBase { } String _joinPath(String basePath, String? part) { - var name; + String name; var pathOnly = Uri.parse(basePath).path; if (part == "" || pathOnly.endsWith("/") || part!.startsWith("/")) { name = "$pathOnly$part"; @@ -54,7 +53,7 @@ abstract class RouterBase { // Router().onGenerateRoute becomes Router() Route? call(RouteSettings settings) => onGenerateRoute(settings); - RouteMatch? findMatch(RouteSettings settings) { + RouteMatchV1? findMatch(RouteSettings settings) { var matcher = RouteMatcher(settings); for (var route in routes) { var match = matcher.match(route); @@ -74,8 +73,8 @@ abstract class RouterBase { return findMatch(RouteSettings(name: path)) != null; } - List allMatches(RouteMatcher matcher) { - var matches = []; + List allMatches(RouteMatcher matcher) { + var matches = []; for (var route in routes) { var matchResult = matcher.match(route); if (matchResult != null) { diff --git a/packages/stacked/lib/src/code_generation/router/router_utils.dart b/lib/src/code_generation/router_annotation/router_utils.dart similarity index 88% rename from packages/stacked/lib/src/code_generation/router/router_utils.dart rename to lib/src/code_generation/router_annotation/router_utils.dart index 41f0a7c74..111768acf 100644 --- a/packages/stacked/lib/src/code_generation/router/router_utils.dart +++ b/lib/src/code_generation/router_annotation/router_utils.dart @@ -20,13 +20,13 @@ PageRoute defaultUnknownRoutePage(RouteSettings settings) => MaterialPageRoute( child: Text( 'Route name ${settings.name} is not found!', textAlign: TextAlign.center, - style: TextStyle(fontSize: 16), + style: const TextStyle(fontSize: 16), ), ), if (!ModalRoute.of(ctx)!.isFirst) OutlinedButton.icon( - label: Text('Back'), - icon: Icon(Icons.arrow_back), + label: const Text('Back'), + icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.of(ctx).pop(), ) ], @@ -48,7 +48,7 @@ PageRoute misTypedArgsRoute(Object args) { const Text( 'Arguments Mistype!', textAlign: TextAlign.center, - style: const TextStyle(fontSize: 20), + style: TextStyle(fontSize: 20), ), const SizedBox(height: 8.0), Text( @@ -57,8 +57,8 @@ PageRoute misTypedArgsRoute(Object args) { ), const SizedBox(height: 16.0), OutlinedButton.icon( - label: Text('Back'), - icon: Icon(Icons.arrow_back), + label: const Text('Back'), + icon: const Icon(Icons.arrow_back), onPressed: () => Navigator.of(ctx).pop(), ) ], @@ -75,9 +75,6 @@ PageRoute buildAdaptivePageRoute({ bool fullscreenDialog = false, String? cupertinoTitle, }) { - assert(builder != null); - assert(maintainState != null); - assert(fullscreenDialog != null); // no transitions for web if (kIsWeb) { return PageRouteBuilder( diff --git a/packages/stacked/lib/src/code_generation/router/stacked_route_wrapper.dart b/lib/src/code_generation/router_annotation/stacked_route_wrapper.dart similarity index 100% rename from packages/stacked/lib/src/code_generation/router/stacked_route_wrapper.dart rename to lib/src/code_generation/router_annotation/stacked_route_wrapper.dart diff --git a/lib/src/code_generation/router_annotation/transitions_builders.dart b/lib/src/code_generation/router_annotation/transitions_builders.dart new file mode 100644 index 000000000..a327fb6e0 --- /dev/null +++ b/lib/src/code_generation/router_annotation/transitions_builders.dart @@ -0,0 +1,131 @@ +// a class that holds a preset of +// common route transition builder +import 'package:flutter/material.dart'; + +class TransitionsBuilders { + static const RouteTransitionsBuilder slideRight = _slideRight; + + static Widget _slideRight(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return SlideTransition( + position: Tween( + begin: const Offset(-1.0, 0.0), + end: Offset.zero, + ).animate(animation), + child: child, + ); + } + + static const RouteTransitionsBuilder slideLeft = _slideLeft; + + static Widget _slideLeft(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return SlideTransition( + position: Tween( + begin: const Offset(1.0, 0.0), + end: Offset.zero, + ).animate(animation), + child: child, + ); + } + + static const RouteTransitionsBuilder slideRightWithFade = _slideRightWithFade; + + static Widget _slideRightWithFade( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return SlideTransition( + position: Tween( + begin: const Offset(-1.0, 0.0), + end: Offset.zero, + ).animate(animation), + child: FadeTransition(opacity: animation, child: child), + ); + } + + static const RouteTransitionsBuilder slideLeftWithFade = _slideLeftWithFade; + + static Widget _slideLeftWithFade( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return SlideTransition( + position: Tween( + begin: const Offset(1.0, 0.0), + end: Offset.zero, + ).animate(animation), + child: FadeTransition(opacity: animation, child: child), + ); + } + + static const RouteTransitionsBuilder slideTop = _slideTop; + + static Widget _slideTop(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return SlideTransition( + position: Tween( + begin: const Offset(0.0, -1.0), + end: Offset.zero, + ).animate(animation), + child: child, + ); + } + + static const RouteTransitionsBuilder slideBottom = _slideBottom; + + static Widget _slideBottom(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return SlideTransition( + position: Tween( + begin: const Offset(0.0, 1.0), + end: Offset.zero, + ).animate(animation), + child: child, + ); + } + + static const RouteTransitionsBuilder fadeIn = _fadeIn; + + static Widget _fadeIn(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return FadeTransition(opacity: animation, child: child); + } + + static const RouteTransitionsBuilder zoomIn = _zoomIn; + + static Widget _zoomIn(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return ScaleTransition(scale: animation, child: child); + } + + static const RouteTransitionsBuilder noTransition = _noTransition; + + static Widget _noTransition(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return child; + } + + static const RouteTransitionsBuilder moveInLeft = _moveInLeft; + + static Widget _moveInLeft(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + Tween t1 = Tween( + begin: const Offset(1.0, 0.0), + end: const Offset(0.0, 0.0), + ); + Tween t5 = Tween( + begin: const Offset(0.0, 0.0), + end: const Offset(-1.0, 0.0), + ); + return SlideTransition( + position: t1.animate(animation), + child: SlideTransition( + position: t5.animate(secondaryAnimation), + child: child, + ), + ); + } +} diff --git a/packages/stacked/lib/src/code_generation/router/uri_extension.dart b/lib/src/code_generation/router_annotation/uri_extension.dart similarity index 100% rename from packages/stacked/lib/src/code_generation/router/uri_extension.dart rename to lib/src/code_generation/router_annotation/uri_extension.dart diff --git a/lib/src/mixins/listenable_service_mixin.dart b/lib/src/mixins/listenable_service_mixin.dart new file mode 100644 index 000000000..1d29df576 --- /dev/null +++ b/lib/src/mixins/listenable_service_mixin.dart @@ -0,0 +1,41 @@ +import 'package:flutter/foundation.dart'; +import 'package:stacked/stacked.dart'; + +/// Adds functionality to easily listen to all reactive values in a service +mixin ListenableServiceMixin { + final List _listeners = List.empty(growable: true); + + int get listenersCount => _listeners.length; + + /// List to the values and react when there are any changes + void listenToReactiveValues(List reactiveValues) { + for (var reactiveValue in reactiveValues) { + if (reactiveValue is ChangeNotifier) { + reactiveValue.addListener(notifyListeners); + } else if (reactiveValue is ReactiveValue) { + reactiveValue.values.listen((value) => notifyListeners()); + } else if (reactiveValue is ReactiveList) { + reactiveValue.onChange.listen((event) => notifyListeners()); + } + } + } + + /// Registers a listener with this service + void addListener(void Function() listener) { + _listeners.add(listener); + } + + /// Removes a listener from the service + void removeListener(void Function() listener) { + _listeners.remove(listener); + } + + /// Notifies all the listeners attached to this service + @protected + @visibleForTesting + void notifyListeners() { + for (var listener in _listeners) { + listener(); + } + } +} diff --git a/packages/stacked/lib/src/state_management/reactive_service_mixin.dart b/lib/src/mixins/reactive_service_mixin.dart similarity index 87% rename from packages/stacked/lib/src/state_management/reactive_service_mixin.dart rename to lib/src/mixins/reactive_service_mixin.dart index 6cd207ec2..4d9e4925a 100644 --- a/packages/stacked/lib/src/state_management/reactive_service_mixin.dart +++ b/lib/src/mixins/reactive_service_mixin.dart @@ -1,10 +1,10 @@ import 'package:flutter/foundation.dart'; - -import '../../stacked.dart'; +import 'package:stacked/stacked.dart'; /// Adds functionality to easily listen to all reactive values in a service +@Deprecated("use ListenableServiceMixin instead") mixin ReactiveServiceMixin { - List _listeners = List.empty(growable: true); + final List _listeners = List.empty(growable: true); /// List to the values and react when there are any changes void listenToReactiveValues(List reactiveValues) { diff --git a/packages/stacked/lib/src/reactive/reactive_list/reactive_list.dart b/lib/src/reactive/reactive_list/reactive_list.dart similarity index 78% rename from packages/stacked/lib/src/reactive/reactive_list/reactive_list.dart rename to lib/src/reactive/reactive_list/reactive_list.dart index 9997ec6f7..d4eab829c 100644 --- a/packages/stacked/lib/src/reactive/reactive_list/reactive_list.dart +++ b/lib/src/reactive/reactive_list/reactive_list.dart @@ -1,4 +1,5 @@ import 'dart:async'; + import 'package:collection/collection.dart'; import 'package:stacked/src/reactive/type_def.dart'; @@ -13,22 +14,24 @@ class ReactiveList extends DelegatingList implements List { _onChange = _changes.stream.asBroadcastStream(); } - ReactiveList.filled(int length, E fill, {bool growable: false}) + ReactiveList.filled(int length, E fill, {bool growable = false}) : super(List.filled(length, fill, growable: growable)) { _onChange = _changes.stream.asBroadcastStream(); } - ReactiveList.from(Iterable elements, {bool growable: true}) + ReactiveList.from(Iterable elements, {bool growable = true}) : super(List.from(elements, growable: growable)) { _onChange = _changes.stream.asBroadcastStream(); } - ReactiveList.of(Iterable elements, {bool growable: true}) + ReactiveList.of(Iterable elements, {bool growable = true}) : super(List.of(elements, growable: growable)); - ReactiveList.generate(int length, E generator(int index), - {bool growable: true}) - : super(List.generate(length, generator, growable: growable)); + ReactiveList.generate( + int length, + E Function(int index) generator, { + bool growable = true, + }) : super(List.generate(length, generator, growable: growable)); /// Adds [element] only if [condition] resolves to true. void addIf(/* bool | Condition */ condition, E element) { @@ -42,6 +45,7 @@ class ReactiveList extends DelegatingList implements List { if (condition is bool && condition) addAll(elements); } + @override operator []=(int index, E value) { super[index] = value; _changes.add(ListChangeNotification.set(value, index)); @@ -49,15 +53,18 @@ class ReactiveList extends DelegatingList implements List { void _add(E element) => super.add(element); - void add(E element) { - super.add(element); - _changes.add(ListChangeNotification.insert(element, length - 1)); + @override + void add(E value) { + super.add(value); + _changes.add(ListChangeNotification.insert(value, length - 1)); } - void addAll(Iterable elements) { - super.addAll(elements); - elements.forEach((element) => - _changes.add(ListChangeNotification.insert(element, length - 1))); + @override + void addAll(Iterable iterable) { + super.addAll(iterable); + for (var element in iterable) { + _changes.add(ListChangeNotification.insert(element, length - 1)); + } } /// Adds only if [element] is not null. @@ -65,20 +72,23 @@ class ReactiveList extends DelegatingList implements List { if (element != null) add(element); } + @override void insert(int index, E element) { super.insert(index, element); _changes.add(ListChangeNotification.insert(element, index)); } - bool remove(final Object? element) { - int pos = indexOf(element as E); - bool hasRemoved = super.remove(element); + @override + bool remove(final Object? value) { + int pos = indexOf(value as E); + bool hasRemoved = super.remove(value); if (hasRemoved) { - _changes.add(ListChangeNotification.remove(element, pos)); + _changes.add(ListChangeNotification.remove(value, pos)); } return hasRemoved; } + @override void clear() { super.clear(); _changes.add(ListChangeNotification.clear()); @@ -106,7 +116,7 @@ class ReactiveList extends DelegatingList implements List { } } -typedef E ChildrenListComposer(S value); +typedef ChildrenListComposer = E Function(S value); /// An Reactive list that is bound to another list [binding] class BoundList extends ReactiveList { @@ -115,10 +125,12 @@ class BoundList extends ReactiveList { final ChildrenListComposer composer; BoundList(this.binding, this.composer) { - for (S v in binding) _add(composer(v)); + for (S v in binding) { + _add(composer(v)); + } binding.onChange.listen((ListChangeNotification n) { if (n.op == ListChangeOp.add) { - insert(n.pos!, composer(n.element!)); + insert(n.pos!, composer(n.element as S)); } else if (n.op == ListChangeOp.remove) { removeAt(n.pos!); } else if (n.op == ListChangeOp.clear) { diff --git a/packages/stacked/lib/src/reactive/reactive_value/proxy_value.dart b/lib/src/reactive/reactive_value/proxy_value.dart similarity index 89% rename from packages/stacked/lib/src/reactive/reactive_value/proxy_value.dart rename to lib/src/reactive/reactive_value/proxy_value.dart index daf55ea09..3bd8f4ab0 100644 --- a/packages/stacked/lib/src/reactive/reactive_value/proxy_value.dart +++ b/lib/src/reactive/reactive_value/proxy_value.dart @@ -19,7 +19,9 @@ class ProxyValue implements ReactiveValue { return ProxyValue._(controller, onChange, getterProxy); } + @override T get value => getterProxy(); + @override set value(T val) { T old = value; if (old == val) { @@ -28,10 +30,12 @@ class ProxyValue implements ReactiveValue { _controller.add(Change(val, old, _curBatch)); } + @override void setCast(dynamic /* T */ val) => value = val; Stream> _onChange; + @override Stream> get onChange { _curBatch++; // ignore: close_sinks @@ -41,15 +45,19 @@ class ProxyValue implements ReactiveValue { return ret.stream.asBroadcastStream(); } + @override Stream get values => onChange.map((c) => c.neu); + @override void bind(ReactiveValue reactive) { value = reactive.value; reactive.values.listen((v) => value = v); } + @override void bindStream(Stream stream) => stream.listen((v) => value = v); + @override void bindOrSet(/* T | Stream | Reactive */ other) { if (other is ReactiveValue) { bind(other); @@ -60,8 +68,10 @@ class ProxyValue implements ReactiveValue { } } + @override StreamSubscription listen(ValueCallback callback) => values.listen(callback); - Stream map(R mapper(T data)) => values.map(mapper); + @override + Stream map(R Function(T data) mapper) => values.map(mapper); } diff --git a/packages/stacked/lib/src/reactive/reactive_value/reactive_value.dart b/lib/src/reactive/reactive_value/reactive_value.dart similarity index 96% rename from packages/stacked/lib/src/reactive/reactive_value/reactive_value.dart rename to lib/src/reactive/reactive_value/reactive_value.dart index ba506b9ed..5af4e74e8 100644 --- a/packages/stacked/lib/src/reactive/reactive_value/reactive_value.dart +++ b/lib/src/reactive/reactive_value/reactive_value.dart @@ -39,7 +39,7 @@ abstract class ReactiveValue { StreamSubscription listen(ValueCallback callback); /// Maps the changes into a [Stream] of [R] - Stream map(R mapper(T data)); + Stream map(R Function(T data) mapper); } /// A record of change in [RxValue] @@ -61,5 +61,6 @@ class Change { DateTime? time, }) : time = DateTime.now(); + @override String toString() => 'Change(new: $neu, old: $old)'; } diff --git a/packages/stacked/lib/src/reactive/reactive_value/stored_value.dart b/lib/src/reactive/reactive_value/stored_value.dart similarity index 89% rename from packages/stacked/lib/src/reactive/reactive_value/stored_value.dart rename to lib/src/reactive/reactive_value/stored_value.dart index c730b65f0..0188f8681 100644 --- a/packages/stacked/lib/src/reactive/reactive_value/stored_value.dart +++ b/lib/src/reactive/reactive_value/stored_value.dart @@ -5,7 +5,9 @@ import '../type_def.dart'; class StoredValue implements ReactiveValue { T _value; + @override T get value => _value; + @override set value(T val) { if (_value == val) { return; @@ -30,8 +32,10 @@ class StoredValue implements ReactiveValue { initial, controller, controller.stream.asBroadcastStream()); } + @override void setCast(dynamic /* T */ val) => value = val; + @override Stream> get onChange { _curBatch++; // ignore: close_sinks @@ -41,15 +45,19 @@ class StoredValue implements ReactiveValue { return ret.stream.asBroadcastStream(); } + @override Stream get values => onChange.map((c) => c.neu); + @override void bind(ReactiveValue reactive) { value = reactive.value; reactive.values.listen((v) => value = v); } + @override void bindStream(Stream stream) => stream.listen((v) => value = v); + @override void bindOrSet(/* T | Stream | Reactive */ other) { if (other is ReactiveValue) { bind(other); @@ -60,8 +68,10 @@ class StoredValue implements ReactiveValue { } } + @override StreamSubscription listen(ValueCallback callback) => values.listen(callback); - Stream map(R mapper(T data)) => values.map(mapper); + @override + Stream map(R Function(T data) mapper) => values.map(mapper); } diff --git a/lib/src/reactive/type_def.dart b/lib/src/reactive/type_def.dart new file mode 100644 index 000000000..528d5dcac --- /dev/null +++ b/lib/src/reactive/type_def.dart @@ -0,0 +1,12 @@ +typedef ValueCallback = void Function(T v); + +/// A callback with no arguments. +/// +/// Intended to listen to events emitted by [Emitter]. +typedef Callback = dynamic Function(); + +typedef Condition = bool Function(); + +typedef ValueGetter = T Function(); + +typedef ValueSetter = void Function(T val); diff --git a/lib/src/router/auto_router_x.dart b/lib/src/router/auto_router_x.dart new file mode 100644 index 000000000..a0544ad62 --- /dev/null +++ b/lib/src/router/auto_router_x.dart @@ -0,0 +1,62 @@ +import 'package:flutter/widgets.dart' show BuildContext, optionalTypeArgs; +import 'package:stacked/src/router/route/page_route_info.dart'; +import 'package:stacked/src/router/widgets/stacked_tabs_router.dart'; + +import 'controller/controller_scope.dart'; +import 'controller/routing_controller.dart'; +import 'matcher/route_match.dart'; +import 'navigation_failure.dart'; +import 'widgets/nested_router.dart'; + +extension StackedRouterExtensions on BuildContext { + StackRouter get router => NestedRouter.of(this); + + StackRouter get watchRouter => NestedRouter.of(this, watch: true); + + @optionalTypeArgs + Future pushRoute(PageRouteInfo route, + {OnNavigationFailure? onFailure}) => + router.push(route, onFailure: onFailure); + + @optionalTypeArgs + Future replaceRoute(PageRouteInfo route, + {OnNavigationFailure? onFailure}) => + router.replace(route, onFailure: onFailure); + + @optionalTypeArgs + Future popRoute([T? result]) => + router.pop(result); + + Future navigateTo(PageRouteInfo route, + {OnNavigationFailure? onFailure}) => + RouterScope.of(this).controller.navigate( + route, + onFailure: onFailure, + ); + + void navigateBack() => RouterScope.of(this).controller.navigateBack(); + + Future navigateNamedTo(String path, + {bool includePrefixMatches = false, + OnNavigationFailure? onFailure}) => + RouterScope.of(this).controller.navigateNamed( + path, + includePrefixMatches: includePrefixMatches, + onFailure: onFailure, + ); + + TabsRouter get tabsRouter => StackedTabsRouter.of(this); + + TabsRouter get watchTabsRouter => StackedTabsRouter.of(this, watch: true); + + // returns the top most rendered route + RouteData get topRoute => watchRouter.topRoute; + + // returns the top most match rendered or pending + RouteMatch get topRouteMatch => watchRouter.topMatch; + + T? innerRouterOf(String routeKey) => + RouterScope.of(this).controller.innerRouterOf(routeKey); + + RouteData get routeData => RouteData.of(this); +} diff --git a/lib/src/router/common/common.dart b/lib/src/router/common/common.dart new file mode 100644 index 000000000..e97dfe119 --- /dev/null +++ b/lib/src/router/common/common.dart @@ -0,0 +1,4 @@ +export 'route_observer.dart'; +export 'route_wrapper.dart'; +export 'parameters.dart'; +export 'transitions_builders.dart'; diff --git a/lib/src/router/common/parameters.dart b/lib/src/router/common/parameters.dart new file mode 100644 index 000000000..c96cce43a --- /dev/null +++ b/lib/src/router/common/parameters.dart @@ -0,0 +1,129 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/cupertino.dart'; + +class Parameters { + final Map _params; + + const Parameters(Map? params) : _params = params ?? const {}; + + Map get rawMap => _params; + + @override + String toString() { + return _params.toString(); + } + + Parameters operator +(Parameters other) => + Parameters({..._params, ...other._params}); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Parameters && + runtimeType == other.runtimeType && + const MapEquality( + values: DeepCollectionEquality(), + ).equals(_params, other._params); + + @override + int get hashCode => const MapEquality().hash(_params); + + bool get isNotEmpty => _params.isNotEmpty; + bool get isEmpty => _params.isEmpty; + + dynamic get(String key, [defaultValue]) { + return _params[key] ?? defaultValue; + } + + String? optString(String key, [String? defaultValue]) => + _params[key] ?? defaultValue; + + String getString(String key, [String? defaultValue]) { + var val = _params[key] ?? defaultValue; + if (val == null) { + throw FlutterError( + 'Failed to parse [String] $key value from ${_params[key]}'); + } + return val; + } + + int? optInt(String key, [int? defaultValue]) { + var param = _params[key]; + if (param == null) { + return defaultValue; + } else if (param is int) { + return param; + } else { + return int.tryParse(param.toString()) ?? defaultValue; + } + } + + int getInt(String key, [int? defaultValue]) { + var val = optInt(key, defaultValue); + if (val == null) { + throw FlutterError( + 'Failed to parse [int] $key value from ${_params[key]}'); + } + return val; + } + + double? optDouble(String key, [double? defaultValue]) { + var param = _params[key]; + if (param == null) { + return defaultValue; + } else if (param is double) { + return param; + } else { + return double.tryParse(param.toString()) ?? defaultValue; + } + } + + double getDouble(String key, [double? defaultValue]) { + var val = optDouble(key, defaultValue); + if (val == null) { + throw FlutterError( + 'Failed to parse [double] $key value from ${_params[key]}'); + } + return val; + } + + num? optNum(String key, [num? defaultValue]) { + var param = _params[key]; + if (param == null) { + return defaultValue; + } else if (param is num) { + return param; + } else { + return double.tryParse(param.toString()) ?? defaultValue; + } + } + + num getNum(String key, [num? defaultValue]) { + var val = optNum(key, defaultValue); + if (val == null) { + throw FlutterError( + 'Failed to parse [num] $key value from ${_params[key]}'); + } + return val; + } + + bool? optBool(String key, [bool? defaultValue]) { + switch (_params[key]?.toLowerCase()) { + case 'true': + return true; + case 'false': + return false; + default: + return defaultValue; + } + } + + bool getBool(String key, [bool? defaultValue]) { + var val = optBool(key, defaultValue); + if (val == null) { + throw FlutterError( + 'Failed to parse [bool] $key value from ${_params[key]}'); + } + return val; + } +} diff --git a/lib/src/router/common/route_observer.dart b/lib/src/router/common/route_observer.dart new file mode 100644 index 000000000..0803da296 --- /dev/null +++ b/lib/src/router/common/route_observer.dart @@ -0,0 +1,184 @@ +import 'package:flutter/widgets.dart'; +import 'package:stacked/src/router/auto_router_x.dart'; +import 'package:stacked/src/router/controller/controller_scope.dart'; +import 'package:stacked/src/router/controller/routing_controller.dart'; +import 'package:stacked/src/router/matcher/route_match.dart'; +import 'package:stacked/src/router/stacked_page.dart'; + +class RouterObserver extends NavigatorObserver { + void didInitTabRoute(TabPageRoute route, TabPageRoute? previousRoute) {} + void didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) {} +} + +abstract class RouteAware { + /// Called when the top route has been popped off, and the current route + /// shows up. + void didPopNext() {} + + /// Called when the current route has been pushed. + void didPush() {} + + /// Called when the current route has been popped off. + void didPop() {} + + /// Called when a new route has been pushed, and the current route is no + /// longer visible. + void didPushNext() {} + + // called when a tab route activates + void didInitTabRoute(TabPageRoute? previousRoute) {} + // called when tab route reactivates + void didChangeTabRoute(TabPageRoute previousRoute) {} +} + +mixin RouteAwareStateMixin on State + implements RouteAware { + RouteObserver? _observer; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // RouterScope exposes the list of provided observers + // including inherited observers + _observer = RouterScope.of(context).firstObserverOfType(); + if (_observer != null) { + // we subscribe to the observer by passing our + // RouteAware state and the scoped routeData + _observer!.subscribe(this, context.routeData); + } + } + + @override + void dispose() { + super.dispose(); + _observer?.unsubscribe(this); + } + + @override + void didChangeTabRoute(TabPageRoute previousRoute) {} + + @override + void didInitTabRoute(TabPageRoute? previousRoute) {} + + @override + void didPop() {} + + @override + void didPush() {} + + @override + void didPopNext() {} + + @override + void didPushNext() {} +} + +class RouteObserver extends RouterObserver { + final Map> _listeners = + >{}; + + /// Subscribe [routeAware] to be informed about changes to [route]. + /// + /// Going forward, [routeAware] will be informed about qualifying changes + /// to [route], e.g. when [route] is covered by another route or when [route] + /// is popped off the [Navigator] stack. + void subscribe(RouteAware routeAware, RouteData route) { + final Set subscribers = + _listeners.putIfAbsent(route.key, () => {}); + if (subscribers.add(routeAware)) { + if (route.router is TabsRouter) { + routeAware.didInitTabRoute(null); + } else { + routeAware.didPush(); + } + } + } + + /// Unsubscribe [routeAware]. + /// + /// [routeAware] is no longer informed about changes to its route. If the given argument was + /// subscribed to multiple types, this will unregister it (once) from each type. + + void unsubscribe(RouteAware routeAware) { + for (final route in _listeners.keys) { + final Set? subscribers = _listeners[route]; + subscribers?.remove(routeAware); + } + } + + @override + void didInitTabRoute(TabPageRoute route, TabPageRoute? previousRoute) { + final List? subscribers = + _listeners[route.routeInfo.key]?.toList(); + if (subscribers != null) { + for (final RouteAware routeAware in subscribers) { + routeAware.didInitTabRoute(previousRoute); + } + } + } + + @override + void didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) { + final List? subscribers = + _listeners[route.routeInfo.key]?.toList(); + if (subscribers != null) { + for (final RouteAware routeAware in subscribers) { + routeAware.didChangeTabRoute(previousRoute); + } + } + } + + @override + void didPop(Route route, Route? previousRoute) { + if (route.settings is StackedPage && + previousRoute?.settings is StackedPage) { + final previousKey = (previousRoute!.settings as StackedPage).routeKey; + final List? previousSubscribers = + _listeners[previousKey]?.toList(); + + if (previousSubscribers != null) { + for (final RouteAware routeAware in previousSubscribers) { + routeAware.didPopNext(); + } + } + final key = (route.settings as StackedPage).routeKey; + + final List? subscribers = _listeners[key]?.toList(); + + if (subscribers != null) { + for (final RouteAware routeAware in subscribers) { + routeAware.didPop(); + } + } + } + } + + @override + void didPush(Route route, Route? previousRoute) { + if (route.settings is StackedPage && + previousRoute?.settings is StackedPage) { + final previousKey = (previousRoute!.settings as StackedPage).routeKey; + final Set? previousSubscribers = _listeners[previousKey]; + + if (previousSubscribers != null) { + for (final RouteAware routeAware in previousSubscribers) { + routeAware.didPushNext(); + } + } + } + } +} + +class TabPageRoute { + final RouteMatch routeInfo; + + final int index; + const TabPageRoute({ + required this.routeInfo, + required this.index, + }); + + String get name => routeInfo.name; + String get path => routeInfo.path; + String get match => routeInfo.stringMatch; +} diff --git a/lib/src/router/common/route_wrapper.dart b/lib/src/router/common/route_wrapper.dart new file mode 100644 index 000000000..b2ee0c19a --- /dev/null +++ b/lib/src/router/common/route_wrapper.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart' show BuildContext, Widget; + +// clients will implement this class to provide a wrapped route. +abstract class RouteWrapper { + Widget wrappedRoute(BuildContext context); +} diff --git a/packages/stacked/lib/src/code_generation/router/transitions_builders.dart b/lib/src/router/common/transitions_builders.dart similarity index 92% rename from packages/stacked/lib/src/code_generation/router/transitions_builders.dart rename to lib/src/router/common/transitions_builders.dart index 403c7d225..b9730da2a 100644 --- a/packages/stacked/lib/src/code_generation/router/transitions_builders.dart +++ b/lib/src/router/common/transitions_builders.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; class TransitionsBuilders { + const TransitionsBuilders._(); + static const RouteTransitionsBuilder slideRight = _slideRight; static Widget _slideRight(BuildContext context, Animation animation, @@ -100,4 +102,11 @@ class TransitionsBuilders { Animation secondaryAnimation, Widget child) { return ScaleTransition(scale: animation, child: child); } + + static const RouteTransitionsBuilder noTransition = _noTransition; + + static Widget _noTransition(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return child; + } } diff --git a/lib/src/router/controller/controller_scope.dart b/lib/src/router/controller/controller_scope.dart new file mode 100644 index 000000000..3955c09df --- /dev/null +++ b/lib/src/router/controller/controller_scope.dart @@ -0,0 +1,100 @@ +import 'package:flutter/widgets.dart'; +import 'package:stacked/src/router/controller/routing_controller.dart'; + +@protected +class RouterScope extends InheritedWidget { + final RoutingController controller; + final NavigatorObserversBuilder inheritableObserversBuilder; + final int stateHash; + final List navigatorObservers; + + const RouterScope({ + Key? key, + required Widget child, + required this.controller, + required this.navigatorObservers, + required this.inheritableObserversBuilder, + required this.stateHash, + }) : super(child: child, key: key); + + static RouterScope of(BuildContext context, {bool watch = false}) { + RouterScope? scope; + if (watch) { + scope = context.dependOnInheritedWidgetOfExactType(); + } else { + scope = context.findAncestorWidgetOfExactType(); + } + assert(() { + if (scope == null) { + throw FlutterError( + 'RouterScope operation requested with a context that does not include a RouterScope.\n' + 'The context used to retrieve the Router must be that of a widget that ' + 'is a descendant of a RouterScope widget.'); + } + return true; + }()); + return scope!; + } + + T? firstObserverOfType() { + final typedObservers = navigatorObservers.whereType(); + if (typedObservers.isNotEmpty) { + return typedObservers.first; + } else { + return null; + } + } + + @override + bool updateShouldNotify(covariant RouterScope oldWidget) { + return stateHash != oldWidget.stateHash; + } +} + +class StackRouterScope extends InheritedWidget { + final StackRouter controller; + final int stateHash; + + const StackRouterScope({ + Key? key, + required Widget child, + required this.controller, + required this.stateHash, + }) : super(child: child, key: key); + + static StackRouterScope? of(BuildContext context, {bool watch = false}) { + if (watch) { + return context.dependOnInheritedWidgetOfExactType(); + } + return context.findAncestorWidgetOfExactType(); + } + + @override + bool updateShouldNotify(covariant StackRouterScope oldWidget) { + return stateHash != oldWidget.stateHash; + } +} + +class TabsRouterScope extends InheritedWidget { + final TabsRouter controller; + final int stateHash; + + const TabsRouterScope({ + Key? key, + required Widget child, + required this.stateHash, + required this.controller, + }) : super(child: child, key: key); + + static TabsRouterScope? of(BuildContext context, {bool watch = false}) { + if (watch) { + return context.dependOnInheritedWidgetOfExactType(); + } + return context.findAncestorWidgetOfExactType(); + } + + @override + bool updateShouldNotify(covariant TabsRouterScope oldWidget) { + return stateHash != oldWidget.stateHash; + } +} diff --git a/lib/src/router/controller/navigation_history/native_navigation_history.dart b/lib/src/router/controller/navigation_history/native_navigation_history.dart new file mode 100644 index 000000000..69d904cd1 --- /dev/null +++ b/lib/src/router/controller/navigation_history/native_navigation_history.dart @@ -0,0 +1,80 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:stacked/src/router/controller/routing_controller.dart'; +import 'package:stacked/src/router/matcher/route_match.dart'; +import 'package:stacked/src/router/parser/route_information_parser.dart'; + +import 'navigation_history_base.dart'; + +class NavigationHistoryImpl extends NavigationHistory { + NavigationHistoryImpl(this.router); + + @override + final StackRouter router; + + final _entries = <_HistoryEntry>[]; + + @override + void onNewUrlState(UrlState newState, {bool notify = true}) { + super.onNewUrlState(newState, notify: notify); + if (_currentUrl == newState.url) return; + _addEntry(newState); + } + + @override + bool get canNavigateBack => length > 1; + + @override + int get length => _entries.length; + + String get _currentUrl => _entries.lastOrNull?.url ?? ''; + + void _addEntry(UrlState urlState) { + if (!urlState.hasSegments) return; + final route = UrlState.toHierarchy(urlState.segments); + // limit history registration to 20 entries + if (_entries.length > 20) { + _entries.removeAt(0); + } + if (urlState.shouldReplace && length > 0) { + _entries.removeLast(); + } + _entries.add(_HistoryEntry(route, urlState.url)); + } + + @override + void back() { + if (canNavigateBack) { + _entries.removeLast(); + router.navigateAll([_entries.last.route]); + } + } + + @override + void forward() { + throw FlutterError( + 'forward navigation is not supported for non-web platforms'); + } +} + +class _HistoryEntry { + final RouteMatch route; + final String url; + + const _HistoryEntry(this.route, this.url); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is _HistoryEntry && + runtimeType == other.runtimeType && + url == other.url; + + @override + int get hashCode => url.hashCode; + + @override + String toString() { + return route.flattened.map((e) => e.name).join('->'); + } +} diff --git a/lib/src/router/controller/navigation_history/navigation_history.dart b/lib/src/router/controller/navigation_history/navigation_history.dart new file mode 100644 index 000000000..a139b592a --- /dev/null +++ b/lib/src/router/controller/navigation_history/navigation_history.dart @@ -0,0 +1,25 @@ +import 'package:stacked/src/router/controller/routing_controller.dart'; + +import 'navigation_history_base.dart'; + +class NavigationHistoryImpl extends NavigationHistory { + NavigationHistoryImpl(StackRouter router); + @override + void back() { + throw Exception("Stub implementation"); + } + + @override + bool get canNavigateBack => throw Exception("Stub implementation"); + + @override + void forward() { + throw Exception("Stub implementation"); + } + + @override + int get length => throw Exception("Stub implementation"); + + @override + StackRouter get router => throw Exception("Stub implementation"); +} diff --git a/lib/src/router/controller/navigation_history/navigation_history_base.dart b/lib/src/router/controller/navigation_history/navigation_history_base.dart new file mode 100644 index 000000000..cab4da3fa --- /dev/null +++ b/lib/src/router/controller/navigation_history/navigation_history_base.dart @@ -0,0 +1,69 @@ +import 'package:flutter/widgets.dart'; +import 'package:stacked/src/router/parser/route_information_parser.dart'; + +import '../routing_controller.dart'; +import 'navigation_history.dart' + if (dart.library.io) 'native_navigation_history.dart' + if (dart.library.html) 'web_navigation_history.dart'; + +abstract class NavigationHistory with ChangeNotifier { + static NavigationHistory create(StackRouter router) { + return NavigationHistoryImpl(router); + } + + void rebuildUrl() { + final newState = UrlState.fromSegments( + router.currentSegments, + shouldReplace: _isUrlStateMarkedForReplace, + ); + _unMarkUrlStateForReplace(); + onNewUrlState(newState); + } + + bool _isUrlStateMarkedForReplace = false; + + bool get isUrlStateMarkedForReplace => _isUrlStateMarkedForReplace; + + void markUrlStateForReplace() => _isUrlStateMarkedForReplace = true; + + void _unMarkUrlStateForReplace() => _isUrlStateMarkedForReplace = false; + + UrlState _urlState = UrlState.fromSegments(const []); + + void onNewUrlState(UrlState newState, {bool notify = true}) { + if (_urlState != newState) { + _urlState = newState; + if (notify) { + notifyListeners(); + } + } + } + + bool isRouteActive(String routeName) { + return urlState.segments.any( + (r) => r.name == routeName, + ); + } + + bool isRouteDataActive(RouteData data) { + return urlState.segments.any( + (route) => route == data.route, + ); + } + + bool isPathActive(String pattern) { + return RegExp(pattern).hasMatch(urlState.path); + } + + UrlState get urlState => _urlState; + + StackRouter get router; + + bool get canNavigateBack; + + int get length; + + void back(); + + void forward(); +} diff --git a/lib/src/router/controller/navigation_history/web_navigation_history.dart b/lib/src/router/controller/navigation_history/web_navigation_history.dart new file mode 100644 index 000000000..62fc9bbd6 --- /dev/null +++ b/lib/src/router/controller/navigation_history/web_navigation_history.dart @@ -0,0 +1,37 @@ +// ignore: avoid_web_libraries_in_flutter +import 'dart:html' as html; + +import 'package:stacked/src/router/controller/routing_controller.dart'; + +import 'navigation_history_base.dart'; + +class NavigationHistoryImpl extends NavigationHistory { + NavigationHistoryImpl(this.router); + + @override + final StackRouter router; + + final _history = html.window.history; + + @override + void back() { + _history.back(); + } + + int get _currentIndex { + final state = _history.state; + if (state is Map) { + return state['serialCount'] ?? 0; + } + return 0; + } + + @override + bool get canNavigateBack => _currentIndex > 0; + + @override + void forward() => _history.forward(); + + @override + int get length => _history.length; +} diff --git a/lib/src/router/controller/nested_router_delegate.dart b/lib/src/router/controller/nested_router_delegate.dart new file mode 100644 index 000000000..eb5b256c2 --- /dev/null +++ b/lib/src/router/controller/nested_router_delegate.dart @@ -0,0 +1,275 @@ +part of 'routing_controller.dart'; + +class NestedRouterDelegate extends RouterDelegate + with ChangeNotifier { + final List? initialRoutes; + final StackRouter controller; + final String? initialDeepLink; + final String? navRestorationScopeId; + final NavigatorObserversBuilder navigatorObservers; + + /// A builder for the placeholder page that is shown + /// before the first route can be rendered. Defaults to + /// an empty page with [Theme.scaffoldBackgroundColor]. + WidgetBuilder? placeholder; + + static List defaultNavigatorObserversBuilder() => const []; + + static NestedRouterDelegate of(BuildContext context) { + final delegate = Router.of(context).routerDelegate; + assert(delegate is NestedRouterDelegate); + return delegate as NestedRouterDelegate; + } + + static reportUrlChanged(BuildContext context, String url) { + Router.of(context) + .routeInformationProvider + ?.routerReportsNewRouteInformation( + RouteInformation( + location: url, + ), + type: RouteInformationReportingType.navigate, + ); + } + + @override + Future popRoute() => controller.topMostRouter().pop(); + + late List _navigatorObservers; + + NestedRouterDelegate( + this.controller, { + this.initialRoutes, + this.placeholder, + this.navRestorationScopeId, + this.initialDeepLink, + this.navigatorObservers = defaultNavigatorObserversBuilder, + }) : assert(initialDeepLink == null || initialRoutes == null) { + _navigatorObservers = navigatorObservers(); + controller.navigationHistory.addListener(_handleRebuild); + } + + factory NestedRouterDelegate.declarative( + RootStackRouter controller, { + required RoutesBuilder routes, + String? navRestorationScopeId, + String? initialDeepLink, + RoutePopCallBack? onPopRoute, + OnNavigateCallBack? onNavigate, + NavigatorObserversBuilder navigatorObservers, + }) = _DeclarativeRouterDelegate; + + UrlState get urlState => controller.navigationHistory.urlState; + + @override + UrlState? get currentConfiguration => urlState; + + @override + Future setInitialRoutePath(UrlState configuration) { + // setInitialRoutePath is re-fired on enabling + // select widget mode from flutter inspector, + // this check is preventing it from rebuilding the app + if (controller.hasEntries) { + return SynchronousFuture(null); + } + + if (initialRoutes?.isNotEmpty == true) { + return controller.pushAll(initialRoutes!); + } else if (initialDeepLink != null) { + return controller.pushNamed(initialDeepLink!, includePrefixMatches: true); + } else if (configuration.hasSegments) { + _onNewUrlState(configuration); + return controller.navigateAll(configuration.segments); + } else { + throw FlutterError("Can not resolve initial route"); + } + } + + @override + Future setNewRoutePath(UrlState configuration) { + final topMost = controller.topMostRouter(); + if (topMost is StackRouter && topMost.hasPagelessTopRoute) { + topMost.popUntil((route) => route.settings is Page); + } + + if (configuration.hasSegments) { + _onNewUrlState(configuration); + return controller.navigateAll(configuration.segments); + } + + notifyListeners(); + return SynchronousFuture(null); + } + + void _onNewUrlState(UrlState state) { + final pathInBrowser = state.uri.path; + var matchedUrlState = state.flatten; + + if (pathInBrowser != matchedUrlState.path) { + matchedUrlState = matchedUrlState.copyWith(replace: true); + } + controller.navigationHistory.onNewUrlState(matchedUrlState); + } + + @override + Widget build(BuildContext context) => _RootRouter( + router: controller, + navigatorObservers: _navigatorObservers, + navigatorObserversBuilder: navigatorObservers, + navRestorationScopeId: navRestorationScopeId, + placeholder: placeholder, + ); + + void _handleRebuild() { + notifyListeners(); + } + + @override + void dispose() { + super.dispose(); + removeListener(_handleRebuild); + controller.dispose(); + } + + void notifyUrlChanged() => _handleRebuild(); +} + +class _RootRouter extends StatefulWidget { + const _RootRouter({ + Key? key, + required this.router, + this.navRestorationScopeId, + this.navigatorObservers = const [], + required this.navigatorObserversBuilder, + this.placeholder, + }) : super(key: key); + final StackRouter router; + final String? navRestorationScopeId; + final List navigatorObservers; + final NavigatorObserversBuilder navigatorObserversBuilder; + + /// A builder for the placeholder page that is shown + /// before the first route can be rendered. Defaults to + /// an empty page with [Theme.scaffoldBackgroundColor]. + final WidgetBuilder? placeholder; + + @override + _RootRouterState createState() => _RootRouterState(); +} + +class _RootRouterState extends State<_RootRouter> { + StackRouter get router => widget.router; + + @override + void initState() { + super.initState(); + router.addListener(_handleRebuild); + } + + @override + void dispose() { + super.dispose(); + router.removeListener(_handleRebuild); + } + + void _handleRebuild() { + if (mounted) { + setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + final stateHash = router.stateHash; + return RouterScope( + controller: router, + navigatorObservers: widget.navigatorObservers, + inheritableObserversBuilder: widget.navigatorObserversBuilder, + stateHash: stateHash, + child: StackRouterScope( + stateHash: stateHash, + controller: router, + child: RouteNavigator( + router: router, + placeholder: widget.placeholder, + navRestorationScopeId: widget.navRestorationScopeId, + navigatorObservers: widget.navigatorObservers, + ), + ), + ); + } +} + +class _DeclarativeRouterDelegate extends NestedRouterDelegate { + final RoutesBuilder routes; + final RoutePopCallBack? onPopRoute; + final OnNavigateCallBack? onNavigate; + + _DeclarativeRouterDelegate( + RootStackRouter router, { + required this.routes, + String? navRestorationScopeId, + String? initialDeepLink, + this.onPopRoute, + this.onNavigate, + NavigatorObserversBuilder navigatorObservers = + NestedRouterDelegate.defaultNavigatorObserversBuilder, + }) : super( + router, + navRestorationScopeId: navRestorationScopeId, + navigatorObservers: navigatorObservers, + initialDeepLink: initialDeepLink, + ) { + router._managedByWidget = true; + } + + @override + Future setInitialRoutePath(UrlState tree) { + if (initialDeepLink != null) { + final routes = controller.buildPageRoutesStack(initialDeepLink!); + controller.pendingRoutesHandler._setPendingRoutes(routes); + } else if (tree.hasSegments) { + final routes = tree.segments.map((e) => e.toPageRouteInfo()).toList(); + controller.pendingRoutesHandler._setPendingRoutes(routes); + } + return SynchronousFuture(null); + } + + @override + Future setNewRoutePath(UrlState tree) async { + return _onNavigate(tree); + } + + Future _onNavigate(UrlState tree) { + if (tree.hasSegments) { + controller.navigateAll(tree.segments); + } + if (onNavigate != null) { + onNavigate!(tree); + } + + return SynchronousFuture(null); + } + + @override + Widget build(BuildContext context) { + final stateHash = controller.stateHash; + return RouterScope( + controller: controller, + inheritableObserversBuilder: navigatorObservers, + stateHash: stateHash, + navigatorObservers: _navigatorObservers, + child: StackRouterScope( + controller: controller, + stateHash: stateHash, + child: RouteNavigator( + router: controller, + declarativeRoutesBuilder: routes, + navRestorationScopeId: navRestorationScopeId, + navigatorObservers: _navigatorObservers, + didPop: onPopRoute, + ), + ), + ); + } +} diff --git a/lib/src/router/controller/pageless_routes_observer.dart b/lib/src/router/controller/pageless_routes_observer.dart new file mode 100644 index 000000000..ed0f82365 --- /dev/null +++ b/lib/src/router/controller/pageless_routes_observer.dart @@ -0,0 +1,42 @@ +import 'package:flutter/widgets.dart'; + +class PagelessRoutesObserver extends NavigatorObserver with ChangeNotifier { + bool _hasPagelessTopRoute = false; + Route? current; + + bool get hasPagelessTopRoute => _hasPagelessTopRoute; + + set hasPagelessTopRoute(value) { + if (value != _hasPagelessTopRoute) { + _hasPagelessTopRoute = value; + notifyListeners(); + } + } + + void _checkCurrentRoute(Route? route) { + current = route; + if (route != null) { + hasPagelessTopRoute = route.settings is! Page; + } + } + + @override + void didPush(Route route, Route? previousRoute) { + _checkCurrentRoute(route); + } + + @override + void didPop(Route route, Route? previousRoute) { + _checkCurrentRoute(previousRoute); + } + + @override + void didRemove(Route route, Route? previousRoute) { + _checkCurrentRoute(previousRoute); + } + + @override + void didReplace({Route? newRoute, Route? oldRoute}) { + _checkCurrentRoute(newRoute); + } +} diff --git a/lib/src/router/controller/root_stack_router.dart b/lib/src/router/controller/root_stack_router.dart new file mode 100644 index 000000000..f980eee2c --- /dev/null +++ b/lib/src/router/controller/root_stack_router.dart @@ -0,0 +1,118 @@ +part of 'routing_controller.dart'; + +typedef PageBuilder = StackedPage Function(RouteData data); +typedef PageFactory = Page Function(RouteData data); + +abstract class RootStackRouter extends StackRouter { + RootStackRouter([GlobalKey? navigatorKey]) + : super( + key: const ValueKey('Root'), + navigatorKey: navigatorKey, + ) { + _navigationHistory = NavigationHistory.create(this); + } + + @override + RouteData get routeData => RouteData( + router: this, + route: const RouteMatch( + name: 'Root', + segments: [''], + path: '', + stringMatch: '', + isBranch: true, + key: ValueKey('Root'), + ), + pendingChildren: [], + ); + + Map get pagesMap; + + List get routes; + + // ignore: prefer_final_fields + bool _managedByWidget = false; + late final NavigationHistory _navigationHistory; + + @override + bool get managedByWidget => _managedByWidget; + + StackedRouteInformationProvider? _lazyInformationProvider; + + StackedRouteInformationProvider routeInfoProvider({ + RouteInformation? initialRouteInformation, + bool Function(String? location)? neglectWhen, + }) { + return _lazyInformationProvider ??= StackedRouteInformationProvider( + initialRouteInformation: initialRouteInformation, + neglectWhen: neglectWhen, + ); + } + + @override + PageBuilder get pageBuilder => _pageBuilder; + + NestedRouterDelegate? _lazyRootDelegate; + + NestedRouterDelegate declarativeDelegate({ + required RoutesBuilder routes, + String? navRestorationScopeId, + RoutePopCallBack? onPopRoute, + String? initialDeepLink, + OnNavigateCallBack? onNavigate, + NavigatorObserversBuilder navigatorObservers = + NestedRouterDelegate.defaultNavigatorObserversBuilder, + }) { + return _lazyRootDelegate ??= NestedRouterDelegate.declarative( + this, + routes: routes, + onNavigate: onNavigate, + initialDeepLink: initialDeepLink, + onPopRoute: onPopRoute, + navRestorationScopeId: navRestorationScopeId, + navigatorObservers: navigatorObservers, + ); + } + + // _lazyRootDelegate is only built one time + NestedRouterDelegate delegate({ + List? initialRoutes, + String? initialDeepLink, + String? navRestorationScopeId, + WidgetBuilder? placeholder, + NavigatorObserversBuilder navigatorObservers = + NestedRouterDelegate.defaultNavigatorObserversBuilder, + }) { + return _lazyRootDelegate ??= NestedRouterDelegate( + this, + initialDeepLink: initialDeepLink, + initialRoutes: initialRoutes, + navRestorationScopeId: navRestorationScopeId, + navigatorObservers: navigatorObservers, + placeholder: placeholder, + ); + } + + DefaultRouteParser defaultRouteParser({bool includePrefixMatches = false}) => + DefaultRouteParser(matcher, includePrefixMatches: includePrefixMatches); + + StackedPage _pageBuilder(RouteData data) { + var builder = pagesMap[data.name]; + assert(builder != null); + return builder!(data) as StackedPage; + } + + @override + void updateRouteData(RouteData data) { + throw FlutterError('Root RouteData should not update'); + } + + @override + late final RouteMatcher matcher = RouteMatcher(routeCollection); + + @override + NavigationHistory get navigationHistory => _navigationHistory; + + @override + late final RouteCollection routeCollection = RouteCollection.from(routes); +} diff --git a/lib/src/router/controller/routing_controller.dart b/lib/src/router/controller/routing_controller.dart new file mode 100644 index 000000000..824680506 --- /dev/null +++ b/lib/src/router/controller/routing_controller.dart @@ -0,0 +1,1409 @@ +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/src/code_generation/router_annotation/parameters.dart'; +import 'package:stacked/src/router/controller/controller_scope.dart'; +import 'package:stacked/src/router/controller/navigation_history/navigation_history_base.dart'; +import 'package:stacked/src/router/matcher/route_match.dart'; +import 'package:stacked/src/router/matcher/route_matcher.dart'; +import 'package:stacked/src/router/navigation_failure.dart'; +import 'package:stacked/src/router/parser/route_information_parser.dart'; +import 'package:stacked/src/router/provider/stacked_route_information_provider.dart'; +import 'package:stacked/src/router/route/page_route_info.dart'; +import 'package:stacked/src/router/route/route_config.dart'; +import 'package:stacked/src/router/route/route_data_scope.dart'; +import 'package:stacked/src/router/stacked_page.dart'; +import 'package:stacked/src/router/transitions/stacked_page_route.dart'; +import 'package:stacked/src/router/widgets/route_navigator.dart'; + +import 'pageless_routes_observer.dart'; + +part '../route/route_data.dart'; +part 'nested_router_delegate.dart'; +part 'root_stack_router.dart'; +part 'stacked_route_guard.dart'; + +typedef RouteDataPredicate = bool Function(RouteData route); +typedef OnNestedNavigateCallBack = void Function(List routes); +typedef OnTabNavigateCallBack = void Function(RouteMatch route); +typedef RoutesBuilder = List Function( + PendingRoutesHandler handler); +typedef RoutePopCallBack = void Function(RouteMatch route, dynamic results); +typedef OnNavigateCallBack = void Function(UrlState tree); +typedef NavigatorObserversBuilder = List Function(); + +abstract class RoutingController with ChangeNotifier { + final _childControllers = []; + + List get childControllers => _childControllers; + final List _pages = []; + + NavigationHistory get navigationHistory => root.navigationHistory; + + void markUrlStateForReplace() => navigationHistory.markUrlStateForReplace(); + + bool _markedForDataUpdate = false; + + void attachChildController(RoutingController childController) { + assert(!_childControllers.contains(childController)); + _childControllers.add(childController); + } + + void removeChildController(RoutingController childController) { + _childControllers.remove(childController); + } + + RoutingController? _innerControllerOf(Key? key) => + _childControllers.lastWhereOrNull( + (c) => c.key == key, + ); + + void _removeTopRouterOf(Key? key) { + for (final ctr in List.unmodifiable(_childControllers).reversed) { + if (ctr.key == key) { + _childControllers.remove(ctr); + break; + } + } + } + + UrlState get urlState => navigationHistory.urlState; + + String get currentPath => urlState.path; + + String get currentUrl => urlState.url; + + void notifyAll({bool forceUrlRebuild = false}) { + notifyListeners(); + if (!isRoot) { + root.notifyListeners(); + } + if (forceUrlRebuild || !isRouteDataActive(current)) { + navigationHistory.rebuildUrl(); + } + } + + List get stackData => + List.unmodifiable(_pages.map((e) => e.routeData)); + + bool isRouteActive(String routeName) { + return navigationHistory.isRouteActive(routeName); + } + + bool isRouteDataActive(RouteData data) { + return navigationHistory.isRouteDataActive(data); + } + + bool isPathActive(String path) { + return navigationHistory.isPathActive(path); + } + + RouteData _createRouteData(RouteMatch route, RouteData parent) { + final routeData = RouteData( + route: route, + router: this, + parent: parent, + pendingChildren: route.children ?? [], + ); + + for (final ctr in _childControllers) { + if (ctr._markedForDataUpdate && ctr.key == routeData.key) { + ctr.updateRouteData(routeData); + ctr._markedForDataUpdate = false; + } + } + + return routeData; + } + + RouteMatch? _matchOrReportFailure( + PageRouteInfo route, [ + OnNavigationFailure? onFailure, + ]) { + var match = matcher.matchByRoute(route); + if (match != null) { + return match; + } else { + if (onFailure != null) { + onFailure(RouteNotFoundFailure(route)); + return null; + } else { + final path = routeCollection.findPathTo(route.routeName); + throw FlutterError( + "\nLooks like you're trying to navigate to a nested route without adding their parent to stack first \n" + "try navigating to ${path.map((e) => e.name).reduce((a, b) => a += ' -> $b')}"); + } + } + } + + List? _matchAllOrReportFailure( + List routes, [ + OnNavigationFailure? onFailure, + ]) { + final matches = []; + for (var route in routes) { + var match = _matchOrReportFailure(route, onFailure); + if (match != null) { + matches.add(match); + } else { + return null; + } + } + return matches; + } + + bool get managedByWidget; + + bool _canHandleNavigation(PageRouteInfo route) { + return routeCollection.containsKey(route.routeName); + } + + _RouterScopeResult? + _findPathScopeOrReportFailure(String path, + {bool includePrefixMatches = false, OnNavigationFailure? onFailure}) { + final routers = topMostRouter(ignorePagelessRoutes: true) + ._buildRoutersHierarchy() + .whereType(); + + for (var router in routers) { + final matches = router.matcher.match( + path, + includePrefixMatches: includePrefixMatches, + ); + if (matches != null) { + return _RouterScopeResult(router, matches); + } + } + if (onFailure != null) { + onFailure( + RouteNotFoundFailure( + PageRouteInfo('', path: path), + ), + ); + } else { + throw FlutterError('Can not navigate to $path'); + } + return null; + } + + RoutingController _findScope( + PageRouteInfo route) { + return topMostRouter(ignorePagelessRoutes: true) + ._buildRoutersHierarchy() + .firstWhere( + (r) => r._canHandleNavigation(route), + orElse: () => this, + ); + } + + Future navigate(PageRouteInfo route, + {OnNavigationFailure? onFailure}) async { + return _findScope(route)._navigate(route, onFailure: onFailure); + } + + void _onNavigate(List routes); + + Future _navigate(PageRouteInfo route, + {OnNavigationFailure? onFailure}) async { + final match = _matchOrReportFailure(route, onFailure); + if (match != null) { + return _navigateAll([match], onFailure: onFailure); + } else { + return SynchronousFuture(null); + } + } + + Future navigateNamed( + String path, { + bool includePrefixMatches = false, + OnNavigationFailure? onFailure, + }) { + final scope = _findPathScopeOrReportFailure( + path, + includePrefixMatches: includePrefixMatches, + onFailure: onFailure, + ); + if (scope != null) { + return scope.router._navigateAll( + scope.matches, + ); + } + return SynchronousFuture(null); + } + + List _buildRoutersHierarchy() { + void collectRouters( + RoutingController currentParent, List all) { + all.add(currentParent); + if (currentParent._parent != null) { + collectRouters(currentParent._parent!, all); + } + } + + final routers = [this]; + if (_parent != null) { + collectRouters(_parent!, routers); + } + return routers; + } + + // should find a way to avoid this + void _updateSharedPathData({ + Map queryParams = const {}, + String fragment = '', + bool includeAncestors = false, + }); + + int get stateHash => const ListEquality().hash(currentSegments); + + LocalKey get key; + + RouteMatcher get matcher; + + List get stack; + + RoutingController? get _parent; + + bool get isTopMost => this == topMostRouter(); + + T? parent() { + return _parent == null ? null : _parent as T; + } + + bool get canNavigateBack => navigationHistory.canNavigateBack; + + void navigateBack() => navigationHistory.back(); + + StackRouter get root => (_parent?.root ?? this) as StackRouter; + + StackRouter? get parentAsStackRouter => parent(); + + bool get isRoot => _parent == null; + + RoutingController topMostRouter({bool ignorePagelessRoutes = false}); + + RouteData? get currentChild; + + RouteData get current; + + RouteData get topRoute => topMostRouter().current; + + RouteMatch get topMatch => topRoute.topMatch; + + RouteData get routeData; + + void updateRouteData(RouteData data); + + RouteCollection get routeCollection; + + StackedPage? get topPage => topMostRouter()._pages.lastOrNull; + + bool get hasEntries => _pages.isNotEmpty; + + int get pageCount => _pages.length; + + T? innerRouterOf(String routeName) { + if (_childControllers.isEmpty) { + return null; + } + return _childControllers.whereType().lastWhereOrNull( + ((c) => c.routeData.name == routeName), + ); + } + + PageBuilder get pageBuilder; + + @optionalTypeArgs + Future pop([T? result]); + + @optionalTypeArgs + Future popTop([T? result]) => + topMostRouter().pop(result); + + @Deprecated('Use canPop instead') + bool get canPopSelfOrChildren; + + bool canPop({ + bool ignoreChildRoutes = false, + bool ignoreParentRoutes = false, + bool ignorePagelessRoutes = false, + }); + + List get currentSegments { + var currentData = currentChild; + final segments = []; + if (currentData != null) { + segments.add(currentData.route); + final childCtrl = _innerControllerOf(currentData.key); + if (childCtrl?.hasEntries == true) { + segments.addAll(childCtrl!.currentSegments); + } else if (currentData.hasPendingChildren) { + segments.addAll( + currentData.pendingChildren.last.flattened, + ); + } + } + return segments; + } + + PageRouteInfo? buildPageRoute(String? path, + {bool includePrefixMatches = true}) { + if (path == null) return null; + return matcher + .match(path, includePrefixMatches: includePrefixMatches) + ?.firstOrNull + ?.toPageRouteInfo(); + } + + List? buildPageRoutesStack(String? path, + {bool includePrefixMatches = true}) { + if (path == null) return null; + return matcher + .match(path, includePrefixMatches: includePrefixMatches) + ?.map((m) => m.toPageRouteInfo()) + .toList(); + } + + @override + String toString() => '${routeData.name} Router'; + + Future _navigateAll(List routes, + {OnNavigationFailure? onFailure}); + + /// Clears all tracked pages and childControllers + void clear() { + _pages.clear(); + _childControllers.clear(); + } +} + +class TabsRouter extends RoutingController { + @override + final RoutingController? _parent; + @override + final LocalKey key; + @override + final RouteCollection routeCollection; + @override + final PageBuilder pageBuilder; + @override + final RouteMatcher matcher; + RouteData _routeData; + int _activeIndex = 0; + int? _previousIndex; + final int homeIndex; + + TabsRouter( + {required this.routeCollection, + required this.pageBuilder, + required this.key, + required RouteData routeData, + this.homeIndex = -1, + RoutingController? parent}) + : matcher = RouteMatcher(routeCollection), + _parent = parent, + _routeData = routeData; + + @override + RouteData get routeData => _routeData; + + @override + void updateRouteData(RouteData data) { + _routeData = data; + for (var page in _pages) { + page.routeData._updateParentData(data); + } + } + + @override + RouteData get current { + return currentChild ?? routeData; + } + + @override + RouteData? get currentChild { + if (_activeIndex < _pages.length) { + return _pages[_activeIndex].routeData; + } else { + return null; + } + } + + int get activeIndex => _activeIndex; + + int? get previousIndex => _previousIndex; + + void setActiveIndex(int index, {bool notify = true}) { + assert(index >= 0 && index < _pages.length); + if (_activeIndex != index) { + _previousIndex = _activeIndex; + _activeIndex = index; + if (notify) { + notifyAll(); + } + } + } + + @override + List get stack => List.unmodifiable(_pages); + + StackedPage? get _activePage { + return _pages.isEmpty ? null : _pages[_activeIndex]; + } + + @override + RoutingController topMostRouter({bool ignorePagelessRoutes = false}) { + var activeKey = _activePage?.routeData.key; + final innerRouter = _innerControllerOf(activeKey); + if (innerRouter != null) { + return innerRouter.topMostRouter( + ignorePagelessRoutes: ignorePagelessRoutes, + ); + } + return this; + } + + @override + @optionalTypeArgs + Future pop([T? result]) { + if (homeIndex != -1 && _activeIndex != homeIndex) { + setActiveIndex(homeIndex); + return SynchronousFuture(true); + } else if (_parent != null) { + return _parent!.pop(result); + } else { + return SynchronousFuture(false); + } + } + + void setupRoutes(List routes) { + final routesToPush = _matchAllOrReportFailure(routes)!; + if (_routeData.hasPendingChildren) { + final preMatchedRoute = _routeData.pendingChildren.last; + final correspondingRouteIndex = routes.indexWhere( + (r) => r.routeName == preMatchedRoute.name, + ); + if (correspondingRouteIndex != -1) { + routesToPush[correspondingRouteIndex] = preMatchedRoute; + _previousIndex = _activeIndex; + _activeIndex = correspondingRouteIndex; + } + } + + if (routesToPush.isNotEmpty) { + _pushAll(routesToPush); + } + _routeData.pendingChildren.clear(); + } + + void _pushAll(List routes) { + for (var route in routes) { + var data = _createRouteData(route, routeData); + _pages.add(pageBuilder(data)); + } + } + + /// Clears all tracked pages + @override + void clear() { + _pages.clear(); + _childControllers.clear(); + } + + void replaceAll( + List routes, PageRouteInfo previousActiveRoute) { + final routesToPush = _matchAllOrReportFailure(routes)!; + + _pages.clear(); + _childControllers.clear(); + _pushAll(routesToPush); + var targetIndex = + routesToPush.indexWhere((r) => r.name == previousActiveRoute.routeName); + if (targetIndex == -1) { + targetIndex = homeIndex == -1 ? 0 : homeIndex; + } + setActiveIndex(targetIndex, notify: false); + } + + @override + Future _navigateAll(List routes, + {OnNavigationFailure? onFailure}) async { + if (routes.isNotEmpty) { + final mayUpdateRoute = routes.last; + + final pageToUpdateIndex = _pages.indexWhere( + (p) => p.routeKey == mayUpdateRoute.key, + ); + + if (pageToUpdateIndex != -1) { + final routeToBeUpdated = _pages[pageToUpdateIndex].routeData._match; + for (final ctr in _childControllers) { + if (ctr.routeData == _pages[pageToUpdateIndex].routeData) { + ctr._markedForDataUpdate = true; + } + } + + final data = _createRouteData(mayUpdateRoute, routeData); + _pages[pageToUpdateIndex] = pageBuilder(data); + + if (_activeIndex != pageToUpdateIndex) { + setActiveIndex(pageToUpdateIndex); + } else if (mayUpdateRoute != routeToBeUpdated) { + notifyAll(); + } + + var mayUpdateController = _innerControllerOf(mayUpdateRoute.key); + if (mayUpdateController != null) { + final newRoutes = mayUpdateRoute.children ?? const []; + return mayUpdateController._navigateAll(newRoutes, + onFailure: onFailure); + } + } + _updateSharedPathData( + queryParams: mayUpdateRoute.queryParams.rawMap, + fragment: mayUpdateRoute.fragment, + includeAncestors: false, + ); + } + + return SynchronousFuture(null); + } + + StackRouter? stackRouterOfIndex(int index) { + if (_childControllers.isEmpty) { + return null; + } + final routeKey = _pages[index].routeData.key; + final innerRouter = _innerControllerOf(routeKey); + if (innerRouter is StackRouter) { + return innerRouter; + } else { + return null; + } + } + + @override + @Deprecated('Use canPop instead') + bool get canPopSelfOrChildren { + final innerRouter = _innerControllerOf(_activePage?.routeKey); + if (innerRouter != null) { + return innerRouter.canPopSelfOrChildren; + } + return false; + } + + @override + bool canPop({ + bool ignoreChildRoutes = false, + bool ignoreParentRoutes = false, + bool ignorePagelessRoutes = false, + }) { + if (ignoreChildRoutes) return false; + + final innerRouter = _innerControllerOf(_activePage?.routeKey); + if (innerRouter != null && + innerRouter.canPop( + ignorePagelessRoutes: ignorePagelessRoutes, + ignoreParentRoutes: true, + )) { + return true; + } + if (!ignoreParentRoutes && _parent != null) { + return _parent!.canPop( + ignoreChildRoutes: true, + ignorePagelessRoutes: ignorePagelessRoutes, + ); + } + return false; + } + + @override + void _updateSharedPathData({ + Map queryParams = const {}, + String fragment = '', + bool includeAncestors = false, + }) { + final newData = _pages[activeIndex].routeData; + final route = newData.route; + newData._updateRoute(route.copyWith( + queryParams: Parameters(queryParams), + fragment: fragment, + )); + if (includeAncestors && _parent != null) { + _parent! + ._updateSharedPathData(queryParams: queryParams, fragment: fragment); + } + } + + @override + void _onNavigate(List routes) {} + + @override + bool get managedByWidget => false; +} + +abstract class StackRouter extends RoutingController { + @override + final RoutingController? _parent; + @override + final LocalKey key; + final GlobalKey _navigatorKey; + final OnNestedNavigateCallBack? onNavigate; + + StackRouter({ + required this.key, + this.onNavigate, + RoutingController? parent, + GlobalKey? navigatorKey, + }) : _navigatorKey = navigatorKey ?? GlobalKey(), + _parent = parent; + + final Map _redirectGuardsListeners = {}; + + late final pendingRoutesHandler = PendingRoutesHandler(); + + void _attachRedirectGuard(RedirectGuardBase guard) { + final stackRouters = _buildRoutersHierarchy().whereType(); + + if (stackRouters + .any((r) => r._redirectGuardsListeners.containsKey(guard))) { + return; + } + + guard.addListener( + _redirectGuardsListeners[guard] = () { + guard._reevaluate(this); + }, + ); + } + + void _removeRedirectGuard(RedirectGuardBase guard) { + guard.removeListener(_redirectGuardsListeners[guard]!); + _redirectGuardsListeners.remove(guard); + } + + @override + void dispose() { + super.dispose(); + _redirectGuardsListeners.forEach( + (guard, listener) { + guard.removeListener(listener); + guard.dispose(); + }, + ); + pagelessRoutesObserver.dispose(); + } + + @override + int get stateHash => super.stateHash ^ hasPagelessTopRoute.hashCode; + + final pagelessRoutesObserver = PagelessRoutesObserver(); + late final activeGuardObserver = ActiveGuardObserver(); + + GlobalKey get navigatorKey => _navigatorKey; + + @override + RouteCollection get routeCollection; + + @override + PageBuilder get pageBuilder; + + @override + RouteMatcher get matcher; + + @override + @Deprecated('Use canPop instead') + bool get canPopSelfOrChildren { + if (_pages.length > 1 || hasPagelessTopRoute) { + return true; + } else if (_pages.isNotEmpty) { + return _innerControllerOf(_pages.last.routeData.key) + ?.canPopSelfOrChildren ?? + false; + } + return false; + } + + @override + bool canPop({ + bool ignoreChildRoutes = false, + bool ignoreParentRoutes = false, + bool ignorePagelessRoutes = false, + }) { + if (_pages.length > 1 || (!ignorePagelessRoutes && hasPagelessTopRoute)) { + return true; + } + + if (!ignoreChildRoutes && _pages.isNotEmpty) { + final innerRouter = _innerControllerOf(_pages.last.routeData.key); + if (innerRouter != null && + innerRouter.canPop( + ignoreParentRoutes: true, + ignorePagelessRoutes: ignorePagelessRoutes, + )) { + return true; + } + } + + if (!ignoreParentRoutes && _parent != null) { + return _parent!.canPop( + ignorePagelessRoutes: ignorePagelessRoutes, + ignoreChildRoutes: true, + ); + } + return false; + } + + @override + RouteData get current => currentChild ?? routeData; + + @override + RouteData? get currentChild { + if (_pages.isNotEmpty) { + return _pages.last.routeData; + } + return null; + } + + // widgets pushed using this method + // don't have paths nor effect url + Future pushWidget( + Widget widget, { + RouteTransitionsBuilder? transitionBuilder, + bool fullscreenDialog = false, + Duration transitionDuration = const Duration(milliseconds: 300), + }) { + final navigator = _navigatorKey.currentState; + assert(navigator != null); + return navigator!.push( + StackedPageRouteBuilder( + child: widget, + fullscreenDialog: fullscreenDialog, + transitionBuilder: transitionBuilder, + transitionDuration: transitionDuration, + ), + ); + } + + // routes pushed using this method + // don't have paths nor effect url + Future pushNativeRoute(Route route) { + final navigator = _navigatorKey.currentState; + assert(navigator != null); + return navigator!.push(route); + } + + @override + RoutingController topMostRouter({bool ignorePagelessRoutes = false}) { + if (_childControllers.isNotEmpty && + (ignorePagelessRoutes || !hasPagelessTopRoute)) { + var topRouteKey = currentChild?.key; + final innerRouter = _innerControllerOf(topRouteKey); + if (innerRouter != null) { + return innerRouter.topMostRouter( + ignorePagelessRoutes: ignorePagelessRoutes, + ); + } + } + return this; + } + + @override + RouteData get topRoute => topMostRouter(ignorePagelessRoutes: true).current; + + bool get hasPagelessTopRoute => pagelessRoutesObserver.hasPagelessTopRoute; + + @override + void _updateSharedPathData({ + Map queryParams = const {}, + String fragment = '', + bool includeAncestors = false, + }) { + for (var index = 0; index < _pages.length; index++) { + final data = _pages[index].routeData; + final route = data.route; + data._updateRoute(route.copyWith( + queryParams: Parameters(queryParams), + fragment: fragment, + )); + } + if (includeAncestors && _parent != null) { + _parent!._updateSharedPathData( + queryParams: queryParams, + fragment: fragment, + includeAncestors: includeAncestors, + ); + } + } + + @override + @optionalTypeArgs + Future pop([T? result]) async { + final NavigatorState? navigator = _navigatorKey.currentState; + if (navigator == null) return SynchronousFuture(false); + if (await navigator.maybePop(result)) { + return true; + } else if (_parent != null) { + return _parent!.pop(result); + } else { + return false; + } + } + + @optionalTypeArgs + void popForced([T? result]) { + final NavigatorState? navigator = _navigatorKey.currentState; + if (navigator != null) { + navigator.pop(result); + } + } + + bool removeLast() => _removeLast(); + + void removeRoute(RouteData route, {bool notify = true}) { + _removeRoute(route._match, notify: notify); + } + + void _removeRoute(RouteMatch route, {bool notify = true}) { + var pageIndex = _pages.lastIndexWhere((p) => p.routeKey == route.key); + if (pageIndex != -1) { + _pages.removeAt(pageIndex); + } + + final stack = _pages.map((e) => e.routeData._match); + for (final guard in route.guards.whereType()) { + if (!stack.any((r) => r.guards.contains(guard))) { + _removeRedirectGuard(guard); + } + } + _updateSharedPathData(includeAncestors: true); + _removeTopRouterOf(route.key); + if (notify) { + notifyAll(forceUrlRebuild: true); + } + } + + @override + void _onNavigate(List routes) { + onNavigate?.call(routes); + } + + bool _removeLast({bool notify = true}) { + var didRemove = false; + if (_pages.isNotEmpty) { + removeRoute(_pages.last.routeData, notify: notify); + didRemove = true; + } + return didRemove; + } + + @override + List get stack => List.unmodifiable(_pages); + + @optionalTypeArgs + Future push(PageRouteInfo route, + {OnNavigationFailure? onFailure}) async { + return _findStackScope(route)._push(route, onFailure: onFailure); + } + + StackRouter _findStackScope(PageRouteInfo route) { + final stackRouters = topMostRouter(ignorePagelessRoutes: true) + ._buildRoutersHierarchy() + .whereType(); + return stackRouters.firstWhere( + (c) => c._canHandleNavigation(route), + orElse: () => this, + ); + } + + Future _popUntilOrPushAll(List routes, + {OnNavigationFailure? onFailure}) async { + final anchor = routes.first; + final anchorPage = _pages.lastWhereOrNull( + (p) => p.routeKey == anchor.key, + ); + + if (anchorPage != null) { + for (var candidate in List.unmodifiable(_pages).reversed) { + _pages.removeLast(); + if (candidate.routeKey == anchorPage.routeKey) { + for (final ctr in _childControllers) { + if (ctr.routeData == candidate.routeData) { + ctr._markedForDataUpdate = true; + } + } + break; + } else { + _removeTopRouterOf(candidate.routeKey); + } + } + } + return _pushAllGuarded( + routes, + onFailure: onFailure, + updateAncestorsPathData: false, + returnLastRouteCompleter: false, + ); + } + + @optionalTypeArgs + Future _push( + PageRouteInfo route, { + OnNavigationFailure? onFailure, + bool notify = true, + }) async { + assert( + !managedByWidget, + 'Pages stack can be managed by either the Widget (StackedRouter.declarative) or the (StackRouter)', + ); + var match = _matchOrReportFailure(route, onFailure); + if (match == null) { + return null; + } + if (await _canNavigate(match, onFailure)) { + _updateSharedPathData( + queryParams: route.rawQueryParams, + fragment: route.fragment, + includeAncestors: true, + ); + return _addEntry(match, notify: notify); + } + return null; + } + + @optionalTypeArgs + Future replace( + PageRouteInfo route, { + OnNavigationFailure? onFailure, + }) { + final scope = _findStackScope(route); + scope._removeLast(notify: false); + markUrlStateForReplace(); + return scope._push(route, onFailure: onFailure); + } + + Future pushAll( + List routes, { + OnNavigationFailure? onFailure, + }) { + assert(routes.isNotEmpty); + return _findStackScope(routes.first)._pushAll( + routes, + onFailure: onFailure, + notify: true, + ); + } + + Future popAndPushAll(List routes, {onFailure}) { + assert(routes.isNotEmpty); + final scope = _findStackScope(routes.first); + scope.pop(); + return scope._pushAll(routes, onFailure: onFailure, notify: true); + } + + Future replaceAll( + List routes, { + OnNavigationFailure? onFailure, + }) { + final scope = _findStackScope(routes.first); + scope._pages.clear(); + markUrlStateForReplace(); + return scope._pushAll(routes, onFailure: onFailure); + } + + void popUntilRoot() { + assert(_navigatorKey.currentState != null); + _navigatorKey.currentState?.popUntil((route) => route.isFirst); + } + + @optionalTypeArgs + Future popAndPush( + PageRouteInfo route, { + TO? result, + OnNavigationFailure? onFailure, + }) { + final scope = _findStackScope(route); + scope.pop(result); + return scope._push(route, onFailure: onFailure); + } + + bool removeUntil(RouteDataPredicate predicate) => _removeUntil(predicate); + + void popUntil(RoutePredicate predicate) { + _navigatorKey.currentState?.popUntil(predicate); + } + + bool _removeUntil(RouteDataPredicate predicate, {bool notify = true}) { + var didRemove = false; + for (var candidate in List.unmodifiable(_pages).reversed) { + if (predicate(candidate.routeData)) { + break; + } else { + _removeLast(notify: false); + didRemove = true; + } + } + if (didRemove && notify) { + notifyAll(forceUrlRebuild: true); + } + return didRemove; + } + + bool removeWhere(RouteDataPredicate predicate, {bool notify = true}) { + var didRemove = false; + for (var entry in List.unmodifiable(_pages)) { + if (predicate(entry.routeData)) { + didRemove = true; + _pages.remove(entry); + } + } + if (notify) { + notifyAll(forceUrlRebuild: true); + } + return didRemove; + } + + void updateDeclarativeRoutes(List routes) async { + _pages.clear(); + final routesToPush = []; + for (var route in routes) { + var match = _matchOrReportFailure(route); + if (match == null) { + break; + } + if (match.guards.isNotEmpty) { + throw FlutterError("Declarative routes can not have guards"); + } + routesToPush.add(match); + final data = _createRouteData(match, routeData); + _pages.add(pageBuilder(data)); + } + + navigationHistory.onNewUrlState( + UrlState.fromSegments( + root.currentSegments, + shouldReplace: current == routeData, + ), + notify: false, + ); + } + + Future _pushAll( + List routes, { + OnNavigationFailure? onFailure, + bool notify = true, + }) async { + final matches = _matchAllOrReportFailure(routes, onFailure); + if (matches != null) { + _pushAllGuarded(matches, onFailure: onFailure, notify: notify); + } + return SynchronousFuture(null); + } + + @optionalTypeArgs + Future _pushAllGuarded( + List routes, { + OnNavigationFailure? onFailure, + bool notify = true, + bool updateAncestorsPathData = true, + bool returnLastRouteCompleter = true, + }) async { + assert( + !managedByWidget, + 'Pages stack can be managed by either the Widget (StackedRouter.declarative) or Router', + ); + + for (var i = 0; i < routes.length; i++) { + var route = routes[i]; + if (await _canNavigate( + route, + onFailure, + pendingRoutes: + routes.whereIndexed((index, element) => index > i).toList(), + )) { + if (i != (routes.length - 1)) { + _addEntry(route, notify: false); + } else { + _updateSharedPathData( + queryParams: route.queryParams.rawMap, + fragment: route.fragment, + includeAncestors: updateAncestorsPathData, + ); + final completer = _addEntry(route, notify: notify); + if (returnLastRouteCompleter) { + return completer; + } + } + } else { + break; + } + } + return SynchronousFuture(null); + } + + Future _addEntry( + RouteMatch route, { + bool notify = true, + }) { + final data = _createRouteData(route, routeData); + final page = pageBuilder(data); + _pages.add(page); + + if (notify) { + notifyAll(); + } + return (page as StackedPage).popped; + } + + Future _canNavigate( + RouteMatch route, + OnNavigationFailure? onFailure, { + List pendingRoutes = const [], + }) async { + if (route.guards.isEmpty) { + return true; + } + for (var guard in route.guards) { + final completer = Completer(); + if (guard is RedirectGuard) { + _attachRedirectGuard(guard); + } + activeGuardObserver.value = guard; + guard.onNavigation( + NavigationResolver( + this, + completer, + route, + pendingRoutes: pendingRoutes, + ), + this); + if (!await completer.future) { + activeGuardObserver.value = null; + if (onFailure != null) { + onFailure(RejectedByGuardFailure(route, guard)); + } + if (guard is RedirectGuard) { + _removeRedirectGuard(guard); + } + return false; + } + activeGuardObserver.value = null; + } + return true; + } + + Future navigateAll( + List routes, { + OnNavigationFailure? onFailure, + }) { + return _navigateAll(routes, onFailure: onFailure); + } + + @override + Future _navigateAll( + List routes, { + OnNavigationFailure? onFailure, + }) async { + if (routes.isNotEmpty) { + if (!managedByWidget) { + _popUntilOrPushAll(routes, onFailure: onFailure); + } else { + _onNavigate(routes); + } + final mayUpdateRoute = routes.last; + final mayUpdateController = _innerControllerOf(mayUpdateRoute.key); + if (mayUpdateController != null) { + final newChildren = mayUpdateRoute.children ?? const []; + if (mayUpdateController.managedByWidget) { + mayUpdateController._onNavigate(newChildren); + } + return mayUpdateController._navigateAll( + newChildren, + onFailure: onFailure, + ); + } + } else if (!managedByWidget) { + _reset(); + } + return SynchronousFuture(null); + } + + void _reset() { + _pages.clear(); + _childControllers.clear(); + } + + @optionalTypeArgs + Future pushAndPopUntil( + PageRouteInfo route, { + required RoutePredicate predicate, + OnNavigationFailure? onFailure, + }) { + final scope = _findStackScope(route); + scope.popUntil(predicate); + return scope._push(route, onFailure: onFailure); + } + + @optionalTypeArgs + Future replaceNamed( + String path, { + bool includePrefixMatches = false, + OnNavigationFailure? onFailure, + }) { + final scope = _findPathScopeOrReportFailure( + path, + includePrefixMatches: includePrefixMatches, + onFailure: onFailure, + ); + if (scope != null) { + scope.router._removeLast(notify: false); + markUrlStateForReplace(); + return scope.router._pushAllGuarded( + scope.matches, + onFailure: onFailure, + ); + } + return SynchronousFuture(null); + } + + @optionalTypeArgs + Future pushNamed( + String path, { + bool includePrefixMatches = false, + OnNavigationFailure? onFailure, + }) { + final scope = _findPathScopeOrReportFailure( + path, + includePrefixMatches: includePrefixMatches, + onFailure: onFailure, + ); + if (scope != null) { + return scope.router._pushAllGuarded( + scope.matches, + onFailure: onFailure, + ); + } + return SynchronousFuture(null); + } + + void popUntilRouteWithName(String name) { + popUntil(ModalRoute.withName(name)); + } + + void popUntilRouteWithPath(String path) { + popUntil((route) { + if ((route.settings is StackedPage)) { + return (route.settings as StackedPage).routeData.match == path; + } + // Assuming pageless routes are either dialogs or bottomSheetModals + // and the user set a path as in RouteSettings(name: path) when showing theme + return route.settings.name == path; + }); + } +} + +class NestedStackRouter extends StackRouter { + @override + final RouteMatcher matcher; + @override + final RouteCollection routeCollection; + @override + final PageBuilder pageBuilder; + @override + final bool managedByWidget; + + RouteData _routeData; + + NestedStackRouter({ + required this.routeCollection, + required this.pageBuilder, + required LocalKey key, + required RouteData routeData, + this.managedByWidget = false, + required RoutingController parent, + OnNestedNavigateCallBack? onNavigate, + GlobalKey? navigatorKey, + }) : matcher = RouteMatcher(routeCollection), + _routeData = routeData, + super( + key: key, + parent: parent, + onNavigate: onNavigate, + navigatorKey: navigatorKey, + ) { + _pushInitialRoutes(); + } + + @override + RouteData get routeData => _routeData; + + @override + void updateRouteData(RouteData data) { + _routeData = data; + for (final page in _pages) { + page.routeData._updateParentData(data); + } + } + + void _pushInitialRoutes() async { + if (_routeData.hasPendingChildren) { + final initialRoutes = List.unmodifiable( + _routeData.pendingChildren, + ); + if (managedByWidget) { + pendingRoutesHandler._setPendingRoutes( + initialRoutes.map((e) => e.toPageRouteInfo()).toList(), + ); + } else { + _pushAllGuarded( + initialRoutes, + returnLastRouteCompleter: false, + ); + } + _routeData.pendingChildren.clear(); + } else { + final possibleInitialRoutes = matcher.match(''); + if (possibleInitialRoutes != null) { + _pushAllGuarded( + possibleInitialRoutes, + returnLastRouteCompleter: false, + ); + } + } + } +} + +class _RouterScopeResult { + final T router; + final List matches; + + const _RouterScopeResult(this.router, this.matches); +} + +class PendingRoutesHandler { + List>? _initialPendingRoutes; + + bool get hasPendingRoutes => _initialPendingRoutes?.isNotEmpty == true; + + void _setPendingRoutes(List? routes) { + _initialPendingRoutes = routes; + } + + List>? get peek => _initialPendingRoutes; + + // one time read pending routes + List>? get initialPendingRoutes { + if (_initialPendingRoutes == null) return null; + final routes = List.of(_initialPendingRoutes!); + _initialPendingRoutes = null; + return routes; + } +} + +class ActiveGuardObserver extends ValueNotifier { + ActiveGuardObserver() : super(null); + + bool get guardInProgress => value != null; +} diff --git a/lib/src/router/controller/stacked_route_guard.dart b/lib/src/router/controller/stacked_route_guard.dart new file mode 100644 index 000000000..4e22d8ce7 --- /dev/null +++ b/lib/src/router/controller/stacked_route_guard.dart @@ -0,0 +1,242 @@ +part of 'routing_controller.dart'; + +abstract class StackedRouteGuard { + /// clients will call [resolver.next(true --> default)] to continue + /// navigation or [resolver.next(false)] to abort navigation + /// example + /* + class AuthGuard extends StackedRouteGuard { + @override + void onNavigation(NavigationResolver resolver, StackRouter router) { + /// resolver.next(true) == we're good, continue navigation + resolver.next(isAuthenticated) + } +} + */ + void onNavigation( + NavigationResolver resolver, + StackRouter router, + ); +} + +class NavigationResolver { + final StackRouter _router; + final Completer _completer; + final RouteMatch route; + final List pendingRoutes; + + NavigationResolver( + this._router, + this._completer, + this.route, { + this.pendingRoutes = const [], + }); + + void next([bool continueNavigation = true]) { + assert(!isResolved, 'Make sure `resolver.next()` is only called once.'); + _completer.complete(continueNavigation); + } + + bool get isResolved => _completer.isCompleted; +} + +abstract class RedirectGuardBase extends StackedRouteGuard with ChangeNotifier { + late ReevaluationStrategy _strategy; + + Future canNavigate(RouteMatch route); + + ReevaluationStrategy get strategy => _strategy; + + // reevaluate the routes allowed by this guard + // when evaluation logic changes + // + // e.g when the user is no longer authenticated + // and there are auth-protected routes in the stack + void reevaluate({ + ReevaluationStrategy strategy = + const ReevaluationStrategy.rePushFirstGuardedRoute(), + }) { + _strategy = strategy; + notifyListeners(); + } + + Future _reevaluate(StackRouter stackRouter) async { + final stack = stackRouter.stackData; + final firstGuardedRoute = stack.firstWhereOrNull( + (r) => r._match.guards.contains(this), + ); + if (firstGuardedRoute != null) { + if (await canNavigate(firstGuardedRoute._match)) { + return; + } + } + _strategy.reevaluate(this, stackRouter); + } +} + +abstract class RedirectGuard extends RedirectGuardBase { + NavigationResolver? _redirectResolver; + + @protected + void redirect(PageRouteInfo route, + {required NavigationResolver resolver}) async { + if (_redirectResolver == resolver) return; + _redirectResolver = resolver; + assert(!resolver.isResolved, 'Resolver is already completed'); + final router = resolver._router._findStackScope(route); + router.push(route).then((_) { + if (!resolver.isResolved) { + resolver.next(false); + } + _redirectResolver = null; + }); + await resolver._completer.future; + if (router.current.name == route.routeName) { + router.markUrlStateForReplace(); + } + router.removeWhere((r) => r.name == route.routeName, notify: false); + _redirectResolver = null; + } + + @override + Future _reevaluate(StackRouter stackRouter) async { + if (_redirectResolver != null) { + onNavigation(_redirectResolver!, stackRouter); + } else { + super._reevaluate(stackRouter); + } + } +} + +abstract class ReevaluationStrategy { + const ReevaluationStrategy._(); + + void reevaluate(RedirectGuardBase guard, StackRouter router); + + const factory ReevaluationStrategy.rePushAllRoutes() = RePushAllStrategy; + + const factory ReevaluationStrategy.rePushFirstGuardedRoute() = + RePushFirstGuarded; + + const factory ReevaluationStrategy.rePushFirstGuardedRouteAndUp() = + RePushFirstGuardedAndUp; + + const factory ReevaluationStrategy.removeFirstGuardedRouteAndUp() = + _RemoveFirstGuardedAndUp; + + const factory ReevaluationStrategy.removeAllAndPush(PageRouteInfo route) = + _RemoveAllAndPush; +} + +class RePushAllStrategy extends ReevaluationStrategy { + const RePushAllStrategy() : super._(); + + @override + void reevaluate(RedirectGuardBase guard, StackRouter router) { + final stackData = router.stackData; + final routesToRemove = + List.unmodifiable(stackData.map((e) => e._match)); + for (final route in routesToRemove) { + router._removeRoute(route, notify: false); + } + + final routesToPush = []; + for (final existingMatch in stackData.map((e) => e.route)) { + final routeToPush = router.matcher.matchByRoute( + existingMatch.toPageRouteInfo(), + ); + if (routeToPush != null) { + routesToPush.add(routeToPush); + } + } + router._pushAllGuarded(routesToPush); + } +} + +class RePushFirstGuarded extends ReevaluationStrategy { + const RePushFirstGuarded() : super._(); + + @override + void reevaluate(RedirectGuardBase guard, StackRouter router) { + final routes = router.stackData.map((e) => e.route).toList(); + final firstGuardedRouteIndex = + routes.indexWhere((r) => r.guards.contains(guard)); + if (firstGuardedRouteIndex == -1) return; + + final routesToRemove = + routes.sublist(firstGuardedRouteIndex, routes.length); + for (final route in routesToRemove) { + router._removeRoute(route, notify: false); + } + // resolve initial child routes if there are any + final routeToPush = router.matcher.matchByRoute( + routes[firstGuardedRouteIndex].toPageRouteInfo(), + ); + if (routeToPush != null) { + router._pushAllGuarded([routeToPush]); + } + } +} + +class RePushFirstGuardedAndUp extends ReevaluationStrategy { + const RePushFirstGuardedAndUp() : super._(); + + @override + void reevaluate(RedirectGuardBase guard, StackRouter router) { + final routes = router.stackData.map((e) => e.route).toList(); + final firstGuardedRouteIndex = + routes.indexWhere((r) => r.guards.contains(guard)); + if (firstGuardedRouteIndex == -1) return; + final routesToRemove = + routes.sublist(firstGuardedRouteIndex, routes.length); + for (final route in routesToRemove) { + router._removeRoute(route, notify: false); + } + + final routesToPush = []; + for (final existingMatch in routes.sublist( + firstGuardedRouteIndex, + routes.length, + )) { + final routeToPush = router.matcher.matchByRoute( + existingMatch.toPageRouteInfo(), + ); + if (routeToPush != null) { + routesToPush.add(routeToPush); + } + } + router._pushAllGuarded(routesToPush); + } +} + +class _RemoveFirstGuardedAndUp extends ReevaluationStrategy { + const _RemoveFirstGuardedAndUp() : super._(); + + @override + void reevaluate(RedirectGuardBase guard, StackRouter router) { + final routes = router.stackData.map((e) => e.route).toList(); + final firstGuardedRouteIndex = + routes.indexWhere((r) => r.guards.contains(guard)); + if (firstGuardedRouteIndex == -1) return; + final routesToRemove = + routes.sublist(firstGuardedRouteIndex, routes.length); + for (final route in routesToRemove) { + router._removeRoute( + route, + notify: route == routesToRemove.last, + ); + } + } +} + +class _RemoveAllAndPush extends ReevaluationStrategy { + final PageRouteInfo route; + + const _RemoveAllAndPush(this.route) : super._(); + + @override + void reevaluate(RedirectGuardBase guard, StackRouter router) { + router._reset(); + router.push(route); + } +} diff --git a/lib/src/router/matcher/route_match.dart b/lib/src/router/matcher/route_match.dart new file mode 100644 index 000000000..1b6c92c1b --- /dev/null +++ b/lib/src/router/matcher/route_match.dart @@ -0,0 +1,130 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:stacked/src/code_generation/router_annotation/parameters.dart'; +import 'package:stacked/src/router/controller/routing_controller.dart'; + +import '../route/page_route_info.dart'; + +@immutable +class RouteMatch { + final Parameters pathParams; + final Parameters queryParams; + final List? children; + final String fragment; + final List segments; + final String? redirectedFrom; + final String name; + final String path; + final String stringMatch; + final T? args; + final List guards; + final LocalKey key; + final bool isBranch; + final Map meta; + + const RouteMatch({ + required this.name, + required this.segments, + required this.path, + required this.stringMatch, + required this.key, + this.isBranch = false, + this.children, + this.args, + this.guards = const [], + this.pathParams = const Parameters({}), + this.queryParams = const Parameters({}), + this.fragment = '', + this.redirectedFrom, + this.meta = const {}, + }); + + bool get hasChildren => children?.isNotEmpty == true; + + bool get fromRedirect => redirectedFrom != null; + + bool get hasEmptyPath => path.isEmpty; + + List allSegments({bool includeEmpty = false}) => [ + if (segments.isEmpty && includeEmpty) '', + ...segments, + if (hasChildren) + ...children!.last.allSegments(includeEmpty: includeEmpty) + ]; + + List get flattened { + return [this, if (hasChildren) ...children!.last.flattened]; + } + + RouteMatch copyWith({ + String? path, + String? stringMatch, + Parameters? pathParams, + Parameters? queryParams, + List? children, + String? fragment, + List? segments, + String? redirectedFrom, + String? routeName, + Object? args, + LocalKey? key, + List? guards, + Map? meta, + }) { + return RouteMatch( + path: path ?? this.path, + stringMatch: stringMatch ?? this.stringMatch, + name: routeName ?? name, + segments: segments ?? this.segments, + children: children ?? this.children, + pathParams: pathParams ?? this.pathParams, + queryParams: queryParams ?? this.queryParams, + fragment: fragment ?? this.fragment, + args: args ?? this.args, + key: key ?? this.key, + guards: guards ?? this.guards, + redirectedFrom: redirectedFrom ?? this.redirectedFrom, + meta: meta ?? this.meta, + ); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is RouteMatch && + runtimeType == other.runtimeType && + path == other.path && + name == other.name && + stringMatch == other.stringMatch && + pathParams == other.pathParams && + key == other.key && + const ListEquality().equals(guards, other.guards) && + queryParams == other.queryParams && + const ListEquality().equals(children, other.children) && + fragment == other.fragment && + redirectedFrom == other.redirectedFrom && + const ListEquality().equals(segments, other.segments) && + const MapEquality().equals(meta, other.meta); + + @override + int get hashCode => + pathParams.hashCode ^ + queryParams.hashCode ^ + const ListEquality().hash(children) ^ + const ListEquality().hash(guards) ^ + fragment.hashCode ^ + redirectedFrom.hashCode ^ + path.hashCode ^ + stringMatch.hashCode ^ + name.hashCode ^ + key.hashCode ^ + const ListEquality().hash(segments) ^ + const MapEquality().hash(meta); + + @override + String toString() { + return 'RouteMatch{ routeName: $name, pathParams: $pathParams, queryParams: $queryParams, children: $children, fragment: $fragment, segments: $segments, redirectedFrom: $redirectedFrom, path: $path, stringMatch: $stringMatch, args: $args, guards: $guards, key: $key}'; + } + + PageRouteInfo toPageRouteInfo() => PageRouteInfo.fromMatch(this); +} diff --git a/lib/src/router/matcher/route_matcher.dart b/lib/src/router/matcher/route_matcher.dart new file mode 100644 index 000000000..d4c456dd6 --- /dev/null +++ b/lib/src/router/matcher/route_matcher.dart @@ -0,0 +1,291 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:path/path.dart' as p; +import 'package:stacked/src/code_generation/router_annotation/parameters.dart'; +import 'package:stacked/src/router/matcher/route_match.dart'; +import 'package:stacked/src/router/route/route_config.dart'; + +import '../route/page_route_info.dart'; + +class RouteCollection { + final Map _routesMap; + + RouteCollection(this._routesMap) : assert(_routesMap.isNotEmpty); + + factory RouteCollection.from(List routes) { + final routesMap = {}; + for (var r in routes) { + routesMap[r.name] = r; + } + return RouteCollection(routesMap); + } + + Iterable get routes => _routesMap.values; + + RouteConfig? operator [](String key) => _routesMap[key]; + + bool containsKey(String key) => _routesMap.containsKey(key); + + RouteCollection subCollectionOf(String key) { + assert(this[key]?.children != null, "$key does not have children"); + return this[key]!.children!; + } + + List findPathTo(String routeName) { + final track = []; + for (final route in routes) { + if (_findPath(route, routeName, track)) { + break; + } + } + return track; + } + + bool _findPath(RouteConfig node, String routeName, List track) { + if (node.name == routeName) { + track.add(node); + return true; + } + + if (node.hasSubTree) { + for (RouteConfig child in node.children!.routes) { + if (_findPath(child, routeName, track)) { + track.insert(0, node); + return true; + } + } + } + + return false; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is RouteCollection && + runtimeType == other.runtimeType && + const MapEquality().equals(_routesMap, other._routesMap); + + @override + int get hashCode => const MapEquality().hash(_routesMap); +} + +class RouteMatcher { + final RouteCollection collection; + + const RouteMatcher(this.collection); + + List? match(String rawPath, {bool includePrefixMatches = false}) { + return _match( + Uri.parse(rawPath), + collection, + includePrefixMatches: includePrefixMatches, + root: true, + ); + } + + List? matchUri(Uri uri, {bool includePrefixMatches = false}) { + return _match( + uri, + collection, + includePrefixMatches: includePrefixMatches, + root: true, + ); + } + + List? _match(Uri uri, RouteCollection collection, + {bool includePrefixMatches = false, + bool root = false, + String? redirectedFrom}) { + final pathSegments = _split(uri.path); + final matches = []; + for (var config in collection.routes) { + var match = matchByPath(uri, config, redirectedFrom: redirectedFrom); + if (match != null) { + if (!includePrefixMatches || config.path == '*') { + matches.clear(); + } + // handle redirects + if (config.isRedirect) { + return _handleRedirect( + routesCollection: collection, + includePrefixMatches: includePrefixMatches, + redirectTo: uri.replace( + path: Uri.parse( + PageRouteInfo.expandPath( + config.redirectTo!, + match.pathParams.rawMap, + ), + ).path), + redirectedFrom: config.path, + ); + } + + if (match.segments.length != pathSegments.length) { + // has rest + if (config.hasSubTree) { + final rest = uri.replace( + pathSegments: pathSegments.sublist(match.segments.length)); + final children = _match(rest, config.children!, + includePrefixMatches: includePrefixMatches); + match = match.copyWith(children: children); + } + matches.add(match); + if (match.allSegments().length >= pathSegments.length) { + break; + } + } else { + // has complete match + // + // include empty route if exists + if (config.hasSubTree && !match.hasChildren) { + match = match.copyWith( + children: _match(uri.replace(path: ''), config.children!)); + } + + matches.add(match); + break; + } + } + } + + if (matches.isEmpty || + (root && + matches.last.allSegments(includeEmpty: true).length < + pathSegments.length)) { + return null; + } + return matches; + } + + List? _handleRedirect({ + required RouteCollection routesCollection, + required bool includePrefixMatches, + required Uri redirectTo, + required String redirectedFrom, + }) { + return _match( + redirectTo, + routesCollection, + includePrefixMatches: includePrefixMatches, + redirectedFrom: redirectedFrom, + ); + } + + List _split(String path) => p.split(path); + + RouteMatch? matchByPath(Uri url, RouteConfig config, + {String? redirectedFrom}) { + var parts = _split(config.path); + var segments = _split(url.path); + + if (parts.length > segments.length) { + return null; + } + + if (config.fullMatch && + segments.length > parts.length && + (parts.isEmpty || parts.last != '*')) { + return null; + } + + var pathParams = {}; + for (var index = 0; index < parts.length; index++) { + var segment = segments[index]; + var part = parts[index]; + if (part.startsWith(':')) { + pathParams[part.substring(1)] = segment; + } else if (segment != part && part != "*") { + return null; + } + } + + var extractedSegments = segments.sublist(0, parts.length); + if (parts.isNotEmpty && parts.last == "*") { + extractedSegments = segments; + } + + final stringMatch = p.joinAll(extractedSegments); + return RouteMatch( + path: config.path, + name: config.name, + meta: config.meta, + isBranch: config.hasSubTree, + key: ValueKey(config.usesPathAsKey ? stringMatch : config.name), + stringMatch: stringMatch, + segments: extractedSegments, + redirectedFrom: redirectedFrom, + guards: config.guards, + pathParams: Parameters(pathParams), + queryParams: Parameters(_normalizeSingleValues(url.queryParametersAll)), + fragment: url.fragment, + ); + } + + RouteMatch? matchByRoute(PageRouteInfo route) { + return _matchByRoute(route, collection); + } + + RouteMatch? _matchByRoute(PageRouteInfo route, RouteCollection routes) { + var config = routes[route.routeName]; + if (config == null) { + return null; + } + var childMatches = []; + if (config.hasSubTree) { + final subRoutes = routes.subCollectionOf(route.routeName); + if (route.hasChildren) { + for (var childRoute in route.initialChildren!) { + var match = _matchByRoute(childRoute, subRoutes); + if (match == null) { + return null; + } else { + childMatches.add(match); + } + } + } else { + // include default matches if exist + final defaultMatches = _match(Uri(path: ''), subRoutes); + if (defaultMatches != null) { + childMatches.addAll(defaultMatches); + } + } + } else if (route.hasChildren) { + return null; + } + return RouteMatch( + name: route.routeName, + segments: _split(route.stringMatch), + path: route.path, + args: route.args, + meta: config.meta, + key: ValueKey( + config.usesPathAsKey ? route.stringMatch : route.routeName, + ), + isBranch: config.hasSubTree, + guards: config.guards, + stringMatch: route.stringMatch, + fragment: route.fragment, + redirectedFrom: route.redirectedFrom, + children: childMatches, + pathParams: Parameters(route.rawPathParams), + queryParams: Parameters(route.rawQueryParams), + ); + } + + Map _normalizeSingleValues( + Map> queryParametersAll) { + final queryMap = {}; + for (var key in queryParametersAll.keys) { + var list = queryParametersAll[key]; + if (list!.length > 1) { + queryMap[key] = list; + } else if (list.isNotEmpty) { + queryMap[key] = list.first; + } else { + queryMap[key] = null; + } + } + return queryMap; + } +} diff --git a/lib/src/router/navigation_failure.dart b/lib/src/router/navigation_failure.dart new file mode 100644 index 000000000..8a415ca8f --- /dev/null +++ b/lib/src/router/navigation_failure.dart @@ -0,0 +1,32 @@ +import 'package:stacked/src/router/controller/routing_controller.dart'; +import 'package:stacked/src/router/matcher/route_match.dart'; +import 'package:stacked/src/router/route/page_route_info.dart'; + +typedef OnNavigationFailure = void Function(NavigationFailure failure); + +abstract class NavigationFailure { + const NavigationFailure(); +} + +class RouteNotFoundFailure extends NavigationFailure { + final PageRouteInfo route; + + const RouteNotFoundFailure(this.route); + + @override + String toString() { + return "Failed to navigate to ${route.fullPath}"; + } +} + +class RejectedByGuardFailure extends NavigationFailure { + final RouteMatch route; + final StackedRouteGuard guard; + + const RejectedByGuardFailure(this.route, this.guard); + + @override + String toString() { + return '${route.stringMatch} rejected by guard ${guard.runtimeType}'; + } +} diff --git a/lib/src/router/parser/route_information_parser.dart b/lib/src/router/parser/route_information_parser.dart new file mode 100644 index 000000000..6c91045fc --- /dev/null +++ b/lib/src/router/parser/route_information_parser.dart @@ -0,0 +1,180 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart' + show RouteInformation, RouteInformationParser; +import 'package:path/path.dart' as p; +import 'package:stacked/src/router/matcher/route_match.dart'; +import 'package:stacked/src/router/matcher/route_matcher.dart'; + +class DefaultRouteParser extends RouteInformationParser { + final RouteMatcher _matcher; + final bool includePrefixMatches; + + DefaultRouteParser(this._matcher, {this.includePrefixMatches = false}); + + @override + Future parseRouteInformation(RouteInformation routeInformation) { + final uri = Uri.parse(routeInformation.location ?? ''); + var matches = + _matcher.matchUri(uri, includePrefixMatches: includePrefixMatches); + return SynchronousFuture(UrlState(uri, matches ?? const [])); + } + + @override + RouteInformation restoreRouteInformation(UrlState configuration) { + return StackedRouteInformation( + location: configuration.url.isEmpty ? '/' : configuration.url, + replace: configuration.shouldReplace, + ); + } +} + +class StackedRouteInformation extends RouteInformation { + final bool replace; + + const StackedRouteInformation({ + required String location, + Object? state, + this.replace = true, + }) : super(location: location, state: state); +} + +@immutable +class UrlState { + final List segments; + final Uri uri; + final bool shouldReplace; + + const UrlState(this.uri, this.segments, {this.shouldReplace = false}); + + String get url => Uri.decodeFull(uri.toString()); + + String get path => uri.path; + + factory UrlState.fromSegments( + List routes, { + bool shouldReplace = false, + }) { + return UrlState( + _buildUri(routes), + routes, + shouldReplace: shouldReplace, + ); + } + + RouteMatch get currentHierarchy => toHierarchy(segments); + + static RouteMatch toHierarchy(List segments) { + if (segments.length == 1) { + return segments.first; + } else { + return segments.first.copyWith(children: [ + toHierarchy( + segments.sublist(1, segments.length), + ), + ]); + } + } + + bool get hasSegments => segments.isNotEmpty; + + RouteMatch? get topMatch => hasSegments ? segments.last : null; + + UrlState get flatten => UrlState.fromSegments(segments.last.flattened); + + RouteMatch? _findSegment( + List segments, + bool Function(RouteMatch segment) predicate, + ) { + for (var segment in segments) { + if (predicate(segment)) { + return segment; + } else if (segment.hasChildren) { + var subSegment = _findSegment(segment.children!, predicate); + if (subSegment != null) { + return subSegment; + } + } + } + return null; + } + + List childrenOfSegmentNamed(String routeName) { + return _findSegment(segments, (match) => match.name == routeName) + ?.children ?? + const []; + } + + static Uri _buildUri(List routes) { + var fullPath = '/'; + if (routes.isEmpty) { + return Uri(path: fullPath); + } + fullPath = p.joinAll( + routes.where((e) => e.stringMatch.isNotEmpty).map( + (e) => e.stringMatch, + ), + ); + final normalized = p.normalize(fullPath); + final lastSegment = routes.last; + Map queryParams = {}; + if (lastSegment.queryParams.isNotEmpty) { + var queries = lastSegment.queryParams.rawMap; + + for (var key in queries.keys) { + var value = _normalizeQueryParamValue(queries[key]); + if (value != null) { + queryParams[key] = value; + } + } + } + + String? fragment; + if (lastSegment.fragment.isNotEmpty == true) { + fragment = lastSegment.fragment; + } + return Uri( + path: normalized, + queryParameters: queryParams.isNotEmpty ? queryParams : null, + fragment: fragment, + ); + } + + static dynamic _normalizeQueryParamValue(dynamic value) { + if (value == null) { + return null; + } + if (value is Iterable) { + return value.map((el) => el?.toString()).toList(); + } + if (value is! String) { + value = value.toString(); + } + if (value.isEmpty) { + return null; + } + return value; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is UrlState && + runtimeType == other.runtimeType && + const ListEquality().equals(segments, other.segments); + + @override + int get hashCode => const ListEquality().hash(segments); + + UrlState copyWith({ + List? segments, + Uri? uri, + bool? replace, + }) { + return UrlState( + uri ?? this.uri, + segments ?? this.segments, + shouldReplace: replace ?? shouldReplace, + ); + } +} diff --git a/lib/src/router/provider/stacked_route_information_provider.dart b/lib/src/router/provider/stacked_route_information_provider.dart new file mode 100644 index 000000000..10bb2cdb3 --- /dev/null +++ b/lib/src/router/provider/stacked_route_information_provider.dart @@ -0,0 +1,106 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stacked/src/router/parser/route_information_parser.dart'; + +class StackedRouteInformationProvider extends RouteInformationProvider + with WidgetsBindingObserver, ChangeNotifier { + /// Create a platform route information provider. + /// + /// Use the [initialRouteInformation] to set the default route information for this + /// provider. + StackedRouteInformationProvider._( + {required RouteInformation initialRouteInformation, this.neglectIf}) + : _value = initialRouteInformation; + + bool Function(String? location)? neglectIf; + + factory StackedRouteInformationProvider( + {RouteInformation? initialRouteInformation, + bool Function(String? location)? neglectWhen}) { + final initialRouteInfo = initialRouteInformation ?? + RouteInformation( + location: PlatformDispatcher.instance.defaultRouteName); + return StackedRouteInformationProvider._( + initialRouteInformation: initialRouteInfo, + neglectIf: neglectWhen, + ); + } + + @override + void routerReportsNewRouteInformation(RouteInformation routeInformation, + {RouteInformationReportingType type = + RouteInformationReportingType.none}) { + if (neglectIf != null && neglectIf!(routeInformation.location)) { + return; + } + + var replace = type == RouteInformationReportingType.neglect || + (type == RouteInformationReportingType.none && + _valueInEngine.location == routeInformation.location); + + if (!replace && routeInformation is StackedRouteInformation) { + replace = routeInformation.replace; + } + + SystemNavigator.selectMultiEntryHistory(); + SystemNavigator.routeInformationUpdated( + location: routeInformation.location!, + state: routeInformation.state, + replace: replace, + ); + _value = routeInformation; + _valueInEngine = routeInformation; + } + + @override + RouteInformation get value => _value; + RouteInformation _value; + + RouteInformation _valueInEngine = + RouteInformation(location: PlatformDispatcher.instance.defaultRouteName); + + void _platformReportsNewRouteInformation(RouteInformation routeInformation) { + if (_value == routeInformation) return; + _value = routeInformation; + _valueInEngine = routeInformation; + notifyListeners(); + } + + @override + void addListener(VoidCallback listener) { + if (!hasListeners) WidgetsBinding.instance.addObserver(this); + super.addListener(listener); + } + + @override + void removeListener(VoidCallback listener) { + super.removeListener(listener); + if (!hasListeners) WidgetsBinding.instance.removeObserver(this); + } + + @override + void dispose() { + // In practice, this will rarely be called. We assume that the listeners + // will be added and removed in a coherent fashion such that when the object + // is no longer being used, there's no listener, and so it will get garbage + // collected. + if (hasListeners) WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + Future didPushRouteInformation( + RouteInformation routeInformation) async { + assert(hasListeners); + _platformReportsNewRouteInformation(routeInformation); + return true; + } + + @override + Future didPushRoute(String route) async { + assert(hasListeners); + _platformReportsNewRouteInformation(RouteInformation(location: route)); + return true; + } +} diff --git a/lib/src/router/route/page_route_info.dart b/lib/src/router/route/page_route_info.dart new file mode 100644 index 000000000..5cbe76f96 --- /dev/null +++ b/lib/src/router/route/page_route_info.dart @@ -0,0 +1,145 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; +import 'package:path/path.dart' as p; +import 'package:stacked/src/code_generation/router_annotation/parameters.dart'; +import 'package:stacked/src/router/auto_router_x.dart'; +import 'package:stacked/src/router/matcher/route_match.dart'; +import 'package:stacked/src/utils.dart'; + +@optionalTypeArgs +@immutable +class PageRouteInfo { + final String _name; + final String path; + final T? args; + final Map rawPathParams; + final Map rawQueryParams; + final List? initialChildren; + final String fragment; + final String? _stringMatch; + final String? redirectedFrom; + + const PageRouteInfo( + this._name, { + required this.path, + this.initialChildren, + this.args, + this.rawPathParams = const {}, + this.rawQueryParams = const {}, + this.fragment = '', + String? stringMatch, + this.redirectedFrom, + }) : _stringMatch = stringMatch; + + String get routeName => _name; + + String get stringMatch { + if (_stringMatch != null) { + return _stringMatch!; + } + return expandPath(path, rawPathParams); + } + + String get fullPath => + p.joinAll([stringMatch, if (hasChildren) initialChildren!.last.fullPath]); + + bool get hasChildren => initialChildren?.isNotEmpty == true; + + bool get fromRedirect => redirectedFrom != null; + + Parameters get pathParams => Parameters(rawPathParams); + + Parameters get queryParams => Parameters(rawQueryParams); + + static String expandPath(String template, Map params) { + if (mapNullOrEmpty(params)) { + return template; + } + var paramsRegex = RegExp(":(${params.keys.join('|')})"); + var path = template.replaceAllMapped(paramsRegex, (match) { + return params[match.group(1)]?.toString() ?? ''; + }); + return path; + } + + List get flattened { + return [this, if (hasChildren) ...initialChildren!.last.flattened]; + } + + PageRouteInfo copyWith({ + String? name, + String? path, + T? args, + RouteMatch? match, + Map? params, + Map? queryParams, + List? children, + String? fragment, + }) { + if ((name == null || identical(name, _name)) && + (path == null || identical(path, this.path)) && + (fragment == null || identical(fragment, this.fragment)) && + (args == null || identical(args, this.args)) && + (params == null || identical(params, rawPathParams)) && + (queryParams == null || identical(queryParams, rawQueryParams)) && + (children == null || identical(children, initialChildren))) { + return this; + } + + return PageRouteInfo( + name ?? _name, + path: path ?? this.path, + args: args ?? this.args, + rawPathParams: params ?? rawPathParams, + rawQueryParams: queryParams ?? rawQueryParams, + initialChildren: children ?? initialChildren, + ); + } + + @override + String toString() { + return 'Route{name: $_name, path: $path, params: $rawPathParams}, children: ${initialChildren?.map((e) => e.routeName)}'; + } + + factory PageRouteInfo.fromMatch(RouteMatch match) { + return PageRouteInfo( + match.name, + path: match.path, + rawPathParams: match.pathParams.rawMap, + rawQueryParams: match.queryParams.rawMap, + fragment: match.fragment, + redirectedFrom: match.redirectedFrom, + stringMatch: match.stringMatch, + args: match.args, + initialChildren: match.children + ?.map( + (m) => PageRouteInfo.fromMatch(m), + ) + .toList(), + ); + } + + Future show(BuildContext context) { + return context.router.push(this); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PageRouteInfo && + _name == other._name && + path == other.path && + fragment == other.fragment && + const ListEquality().equals(initialChildren, other.initialChildren) && + const MapEquality().equals(rawPathParams, other.rawPathParams) && + const MapEquality().equals(rawQueryParams, other.rawQueryParams); + + @override + int get hashCode => + _name.hashCode ^ + path.hashCode ^ + fragment.hashCode ^ + const MapEquality().hash(rawPathParams) ^ + const MapEquality().hash(rawQueryParams) ^ + const ListEquality().hash(initialChildren); +} diff --git a/lib/src/router/route/route_config.dart b/lib/src/router/route/route_config.dart new file mode 100644 index 000000000..06f104d5d --- /dev/null +++ b/lib/src/router/route/route_config.dart @@ -0,0 +1,40 @@ +import 'package:stacked/src/router/controller/routing_controller.dart'; + +import '../matcher/route_matcher.dart'; + +class RouteConfig { + final String name; + final String path; + final bool fullMatch; + final RouteCollection? _children; + final String? redirectTo; + final List guards; + final bool usesPathAsKey; + final String? parent; + final Map meta; + final bool deferredLoading; + + RouteConfig( + this.name, { + required this.path, + this.usesPathAsKey = false, + this.guards = const [], + this.fullMatch = false, + this.redirectTo, + this.parent, + this.meta = const {}, + this.deferredLoading = false, + List? children, + }) : _children = children != null ? RouteCollection.from(children) : null; + + bool get hasSubTree => _children != null; + + RouteCollection? get children => _children; + + bool get isRedirect => redirectTo != null; + + @override + String toString() { + return 'RouteConfig{name: $name}'; + } +} diff --git a/lib/src/router/route/route_data.dart b/lib/src/router/route/route_data.dart new file mode 100644 index 000000000..61c3ce673 --- /dev/null +++ b/lib/src/router/route/route_data.dart @@ -0,0 +1,104 @@ +part of '../controller/routing_controller.dart'; + +class RouteData { + RouteMatch _match; + RouteData? _parent; + final RoutingController router; + + LocalKey get key => _match.key; + + RouteData({ + required RouteMatch route, + required this.router, + RouteData? parent, + required this.pendingChildren, + }) : _match = route, + _parent = parent; + + final List pendingChildren; + + bool get isActive => router.isRouteActive(name); + + bool get hasPendingChildren => pendingChildren.isNotEmpty; + + static RouteData of(BuildContext context) { + return RouteDataScope.of(context).routeData; + } + + bool get hasPendingSubNavigation => + hasPendingChildren && pendingChildren.last.hasChildren; + + T argsAs({T Function()? orElse}) { + final args = _match.args; + if (args == null) { + if (orElse == null) { + throw FlutterError( + '${T.toString()} can not be null because it has a required parameter'); + } else { + return orElse(); + } + } else if (args is! T) { + throw FlutterError( + 'Expected [${T.toString()}], found [${args.runtimeType}]'); + } else { + return args; + } + } + + void _updateRoute(RouteMatch value) { + if (_match != value) { + _match = value; + } + } + + void _updateParentData(RouteData value) { + _parent = value; + } + + RouteMatch get route => _match; + + String get name => _match.name; + + String get path => _match.path; + + RouteData? get parent => _parent; + + Map get meta => _match.meta; + + Object? get args => _match.args; + + String get match => _match.stringMatch; + + List get breadcrumbs => List.unmodifiable([ + if (_parent != null) ..._parent!.breadcrumbs, + _match, + ]); + + Parameters get inheritedPathParams { + final params = breadcrumbs.map((e) => e.pathParams).reduce( + (value, element) => value + element, + ); + return params; + } + + Parameters get pathParams => _match.pathParams; + + Parameters get queryParams => _match.queryParams; + + String get fragment => _match.fragment; + + RouteMatch _getTopMatch(RouteMatch routeMatch) { + if (routeMatch.hasChildren) { + return _getTopMatch(routeMatch.children!.last); + } else { + return routeMatch; + } + } + + RouteMatch get topMatch { + if (hasPendingChildren) { + return _getTopMatch(pendingChildren.last); + } + return _match; + } +} diff --git a/lib/src/router/route/route_data_scope.dart b/lib/src/router/route/route_data_scope.dart new file mode 100644 index 000000000..4dce1ba8f --- /dev/null +++ b/lib/src/router/route/route_data_scope.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/src/router/controller/routing_controller.dart'; + +class RouteDataScope extends InheritedWidget { + final RouteData routeData; + + const RouteDataScope({ + Key? key, + required this.routeData, + required Widget child, + }) : super(child: child, key: key); + + static RouteDataScope of(BuildContext context) { + var scope = context.findAncestorWidgetOfExactType(); + assert(() { + if (scope == null) { + throw FlutterError( + 'RouteData operation requested with a context that does not include an RouteData.\n' + 'The context used to retrieve the RouteData must be that of a widget that ' + 'is a descendant of a StackedPage.'); + } + return true; + }()); + return scope!; + } + + @override + bool updateShouldNotify(covariant RouteDataScope oldWidget) { + return routeData.route != oldWidget.routeData.route; + } +} diff --git a/lib/src/router/router_service_interface.dart b/lib/src/router/router_service_interface.dart new file mode 100644 index 000000000..9b5bf9209 --- /dev/null +++ b/lib/src/router/router_service_interface.dart @@ -0,0 +1,7 @@ +import 'package:stacked/stacked.dart'; + +abstract class RouterServiceInterface { + RootStackRouter get router; + + RouteData get topRoute; +} diff --git a/lib/src/router/stacked_page.dart b/lib/src/router/stacked_page.dart new file mode 100644 index 000000000..9e3441a66 --- /dev/null +++ b/lib/src/router/stacked_page.dart @@ -0,0 +1,406 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/src/router/controller/routing_controller.dart'; +import 'package:stacked/src/router/route/route_data_scope.dart'; +import 'package:stacked/src/router/widgets/custom_cupertino_transitions_builder.dart'; + +import 'common/route_wrapper.dart'; +import 'widgets/wrapped_route.dart'; + +abstract class StackedPage extends Page { + final RouteData routeData; + final Widget _child; + final bool fullscreenDialog; + final bool maintainState; + final bool opaque; + + final _popCompleter = Completer(); + + Future get popped => _popCompleter.future; + + Widget get child => _child; + + StackedPage({ + required this.routeData, + required Widget child, + this.fullscreenDialog = false, + this.maintainState = true, + this.opaque = true, + LocalKey? key, + }) : _child = child is RouteWrapper + ? WrappedRoute( + child: child as RouteWrapper, + ) + : child, + super( + restorationId: routeData.name, + name: routeData.name, + arguments: routeData.route.args, + ); + + @override + bool canUpdate(Page other) { + return other.runtimeType == runtimeType && + (other as StackedPage).routeKey == routeKey; + } + + LocalKey get routeKey => routeData.key; + + Widget buildPage(BuildContext context) { + return RouteDataScope( + routeData: routeData, + child: _child, + ); + } + + Route onCreateRoute(BuildContext context); + + @override + Route createRoute(BuildContext context) { + return onCreateRoute(context) + ..popped.then( + _popCompleter.complete, + ); + } +} + +class MaterialPageX extends StackedPage { + MaterialPageX({ + required RouteData routeData, + required Widget child, + bool fullscreenDialog = false, + bool maintainState = true, + LocalKey? key, + }) : super( + routeData: routeData, + child: child, + maintainState: maintainState, + fullscreenDialog: fullscreenDialog, + key: key, + ); + + @override + Route onCreateRoute(BuildContext context) { + return PageBasedMaterialPageRoute(page: this); + } +} + +class PageBasedMaterialPageRoute extends PageRoute + with MaterialRouteTransitionMixin { + PageBasedMaterialPageRoute({ + required StackedPage page, + }) : super(settings: page); + + StackedPage get _page => settings as StackedPage; + + List scopes = []; + + @override + Widget buildContent(BuildContext context) => _page.buildPage(context); + + @override + bool get maintainState => _page.maintainState; + + @override + bool get fullscreenDialog => _page.fullscreenDialog; + + @override + String get debugLabel => '${super.debugLabel}(${_page.name})'; +} + +class _CustomPageBasedPageRouteBuilder extends PageRoute + with _CustomPageRouteTransitionMixin { + _CustomPageBasedPageRouteBuilder({ + required StackedPage page, + }) : super(settings: page); + + @override + Widget buildContent(BuildContext context) => _page.buildPage(context); + + @override + bool get maintainState => _page.maintainState; + + @override + bool get fullscreenDialog => _page.fullscreenDialog; + + @override + String get debugLabel => '${super.debugLabel}(${_page.name})'; +} + +class _NoAnimationPageRouteBuilder extends PageRoute + with _NoAnimationPageRouteTransitionMixin { + _NoAnimationPageRouteBuilder({ + required StackedPage page, + }) : super(settings: page); + + @override + Widget buildContent(BuildContext context) => _page.buildPage(context); + + @override + bool get maintainState => _page.maintainState; + + @override + bool get fullscreenDialog => _page.fullscreenDialog; + + @override + String get debugLabel => '${super.debugLabel}(${_page.name})'; + + @override + Duration get transitionDuration => Duration.zero; +} + +mixin _NoAnimationPageRouteTransitionMixin on PageRoute { + /// Builds the primary contents of the route. + StackedPage get _page => settings as StackedPage; + + @protected + Widget buildContent(BuildContext context); + + @override + bool get barrierDismissible => false; + + @override + Color? get barrierColor => null; + + @override + String? get barrierLabel => null; + + @override + bool get opaque => _page.opaque; + + @override + bool canTransitionTo(TransitionRoute nextRoute) { + // Don't perform outgoing animation if the next route is a fullscreen dialog. + return (nextRoute is _CustomPageBasedPageRouteBuilder && + !nextRoute.fullscreenDialog || + nextRoute is MaterialRouteTransitionMixin && + !nextRoute.fullscreenDialog) || + (nextRoute is _NoAnimationPageRouteTransitionMixin && + !nextRoute.fullscreenDialog) || + (nextRoute is CupertinoRouteTransitionMixin && + !nextRoute.fullscreenDialog); + } + + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return Semantics( + scopesRoute: true, + explicitChildNodes: true, + child: buildContent(context), + ); + } +} + +mixin _CustomPageRouteTransitionMixin on PageRoute { + /// Builds the primary contents of the route. + CustomPage get _page => settings as CustomPage; + + @protected + Widget buildContent(BuildContext context); + + @override + Duration get transitionDuration => Duration( + milliseconds: _page.durationInMilliseconds, + ); + + @override + Duration get reverseTransitionDuration => Duration( + milliseconds: _page.reverseDurationInMilliseconds, + ); + + @override + bool get barrierDismissible => _page.barrierDismissible; + + @override + Color? get barrierColor => + _page.barrierColor == null ? null : Color(_page.barrierColor!); + + @override + String? get barrierLabel => _page.barrierLabel; + + @override + bool get opaque => _page.opaque; + + @override + bool canTransitionTo(TransitionRoute nextRoute) { + // Don't perform outgoing animation if the next route is a fullscreen dialog. + return (nextRoute is MaterialRouteTransitionMixin && + !nextRoute.fullscreenDialog) || + (nextRoute is _NoAnimationPageRouteTransitionMixin && + !nextRoute.fullscreenDialog) || + (nextRoute is CupertinoRouteTransitionMixin && + !nextRoute.fullscreenDialog); + } + + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return Semantics( + scopesRoute: true, + explicitChildNodes: true, + child: buildContent(context), + ); + } + + Widget _defaultTransitionsBuilder( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child) { + return child; + } + + @override + Widget buildTransitions(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + final transitionsBuilder = + _page.transitionsBuilder ?? _defaultTransitionsBuilder; + return transitionsBuilder(context, animation, secondaryAnimation, child); + } +} + +abstract class _TitledAutoRoutePage extends StackedPage { + final String? title; + + _TitledAutoRoutePage({ + required RouteData routeData, + required Widget child, + this.title, + bool fullscreenDialog = false, + bool maintainState = true, + bool opaque = true, + }) : super( + routeData: routeData, + child: child, + maintainState: maintainState, + fullscreenDialog: fullscreenDialog, + opaque: opaque, + ); +} + +class CupertinoPageX extends _TitledAutoRoutePage { + CupertinoPageX({ + required RouteData routeData, + required Widget child, + String? title, + bool fullscreenDialog = false, + bool maintainState = true, + }) : super( + routeData: routeData, + child: child, + maintainState: maintainState, + fullscreenDialog: fullscreenDialog, + title: title, + ); + + @override + Route onCreateRoute(BuildContext context) { + return _PageBasedCupertinoPageRoute(page: this); + } +} + +class _PageBasedCupertinoPageRoute extends PageRoute + with CustomCupertinoRouteTransitionMixin { + _PageBasedCupertinoPageRoute({ + required _TitledAutoRoutePage page, + }) : super(settings: page); + + _TitledAutoRoutePage get _page => settings as _TitledAutoRoutePage; + + @override + Widget buildContent(BuildContext context) => _page.buildPage(context); + + @override + String? get title => _page.title; + + @override + bool get maintainState => _page.maintainState; + + @override + bool get fullscreenDialog => _page.fullscreenDialog; + + @override + String get debugLabel => '${super.debugLabel}(${_page.name})'; +} + +class AdaptivePage extends _TitledAutoRoutePage { + AdaptivePage({ + required RouteData routeData, + required Widget child, + String? title, + bool fullscreenDialog = false, + bool maintainState = true, + bool opaque = true, + }) : super( + routeData: routeData, + child: child, + title: title, + maintainState: maintainState, + fullscreenDialog: fullscreenDialog, + opaque: opaque, + ); + + @override + Route onCreateRoute(BuildContext context) { + if (kIsWeb) { + return _NoAnimationPageRouteBuilder(page: this); + } + + return PageBasedMaterialPageRoute(page: this); + } +} + +typedef CustomRouteBuilder = Route Function( + BuildContext context, Widget child, CustomPage page); + +class CustomPage extends StackedPage { + final int durationInMilliseconds; + final int reverseDurationInMilliseconds; + final int? barrierColor; + final bool barrierDismissible; + final String? barrierLabel; + final RouteTransitionsBuilder? transitionsBuilder; + final CustomRouteBuilder? customRouteBuilder; + + CustomPage({ + required RouteData routeData, + required Widget child, + bool fullscreenDialog = false, + bool maintainState = true, + bool opaque = true, + this.durationInMilliseconds = 300, + this.reverseDurationInMilliseconds = 300, + this.barrierColor, + this.barrierDismissible = false, + this.barrierLabel, + this.transitionsBuilder, + this.customRouteBuilder, + LocalKey? key, + }) : super( + routeData: routeData, + key: key, + child: child, + maintainState: maintainState, + fullscreenDialog: fullscreenDialog, + opaque: opaque, + ); + + @override + Route onCreateRoute(BuildContext context) { + final result = buildPage(context); + if (customRouteBuilder != null) { + return customRouteBuilder!(context, result, this); + } + return _CustomPageBasedPageRouteBuilder(page: this); + } +} diff --git a/lib/src/router/transitions/stacked_page_route.dart b/lib/src/router/transitions/stacked_page_route.dart new file mode 100644 index 000000000..4098cee5f --- /dev/null +++ b/lib/src/router/transitions/stacked_page_route.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +class StackedPageRouteBuilder extends PageRoute { + StackedPageRouteBuilder({ + this.transitionBuilder, + this.transitionDuration = const Duration(milliseconds: 300), + required this.child, + bool fullscreenDialog = false, + }) : super(fullscreenDialog: fullscreenDialog); + + final RouteTransitionsBuilder? transitionBuilder; + final Widget child; + + @override + Color? get barrierColor => null; + + @override + String? get barrierLabel => null; + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + return child; + } + + @override + Widget buildTransitions(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + if (transitionBuilder != null) { + return transitionBuilder!( + context, + animation, + secondaryAnimation, + child, + ); + } + final theme = Theme.of(context); + return theme.pageTransitionsTheme.buildTransitions( + this, + context, + animation, + secondaryAnimation, + child, + ); + } + + @override + bool get maintainState => true; + + @override + final Duration transitionDuration; +} diff --git a/lib/src/router/widgets/auto_tabs_scaffold.dart b/lib/src/router/widgets/auto_tabs_scaffold.dart new file mode 100644 index 000000000..ddc09da7e --- /dev/null +++ b/lib/src/router/widgets/auto_tabs_scaffold.dart @@ -0,0 +1,151 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/src/router/auto_router_x.dart'; +import 'package:stacked/src/router/controller/routing_controller.dart'; +import 'package:stacked/src/router/route/page_route_info.dart'; +import 'package:stacked/src/router/widgets/stacked_tabs_router.dart'; + +typedef AppBarBuilder = PreferredSizeWidget Function( + BuildContext context, + TabsRouter tabsRouter, +); + +typedef BottomNavigationBuilder = Widget Function( + BuildContext context, + TabsRouter tabsRouter, +); + +typedef FloatingActionButtonBuilder = Widget? Function( + BuildContext context, + TabsRouter tabsRouter, +); + +class StackedTabsScaffold extends StatelessWidget { + final AnimatedIndexedStackBuilder? builder; + final List routes; + final Duration animationDuration; + final Curve animationCurve; + final bool lazyLoad; + final BottomNavigationBuilder? bottomNavigationBuilder; + final NavigatorObserversBuilder navigatorObservers; + final bool inheritNavigatorObservers; + final Widget? floatingActionButton; + final FloatingActionButtonBuilder? floatingActionButtonBuilder; + final FloatingActionButtonLocation? floatingActionButtonLocation; + final FloatingActionButtonAnimator? floatingActionButtonAnimator; + final List? persistentFooterButtons; + final Widget? drawer; + final DrawerCallback? onDrawerChanged; + final Widget? endDrawer; + final DrawerCallback? onEndDrawerChanged; + final Color? drawerScrimColor; + final Color? backgroundColor; + final Widget? bottomSheet; + final bool? resizeToAvoidBottomInset; + final bool primary; + final DragStartBehavior drawerDragStartBehavior; + final double? drawerEdgeDragWidth; + final bool drawerEnableOpenDragGesture; + final bool endDrawerEnableOpenDragGesture; + final String? restorationId; + final bool extendBody; + final bool extendBodyBehindAppBar; + final AppBarBuilder? appBarBuilder; + final GlobalKey? scaffoldKey; + final int homeIndex; + const StackedTabsScaffold({ + Key? key, + required this.routes, + this.lazyLoad = true, + this.homeIndex = -1, + this.animationDuration = const Duration(milliseconds: 300), + this.animationCurve = Curves.ease, + this.builder, + this.bottomNavigationBuilder, + this.inheritNavigatorObservers = true, + this.navigatorObservers = + NestedRouterDelegate.defaultNavigatorObserversBuilder, + this.floatingActionButton, + this.floatingActionButtonBuilder, + this.floatingActionButtonLocation, + this.floatingActionButtonAnimator, + this.persistentFooterButtons, + this.drawer, + this.onDrawerChanged, + this.endDrawer, + this.onEndDrawerChanged, + this.drawerScrimColor, + this.backgroundColor, + this.bottomSheet, + this.resizeToAvoidBottomInset, + this.primary = true, + this.drawerDragStartBehavior = DragStartBehavior.start, + this.drawerEdgeDragWidth, + this.drawerEnableOpenDragGesture = true, + this.endDrawerEnableOpenDragGesture = true, + this.restorationId, + this.extendBody = false, + this.extendBodyBehindAppBar = false, + this.appBarBuilder, + this.scaffoldKey, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return StackedTabsRouter( + routes: routes, + duration: animationDuration, + lazyLoad: lazyLoad, + homeIndex: homeIndex, + navigatorObservers: navigatorObservers, + inheritNavigatorObservers: inheritNavigatorObservers, + curve: animationCurve, + builder: (context, child, animation) { + final tabsRouter = context.tabsRouter; + return Scaffold( + key: scaffoldKey, + extendBodyBehindAppBar: extendBodyBehindAppBar, + endDrawer: endDrawer, + extendBody: extendBody, + restorationId: restorationId, + resizeToAvoidBottomInset: resizeToAvoidBottomInset, + backgroundColor: backgroundColor, + drawer: drawer, + drawerDragStartBehavior: drawerDragStartBehavior, + drawerEdgeDragWidth: drawerEdgeDragWidth, + drawerEnableOpenDragGesture: drawerEnableOpenDragGesture, + drawerScrimColor: drawerScrimColor, + onDrawerChanged: onDrawerChanged, + endDrawerEnableOpenDragGesture: endDrawerEnableOpenDragGesture, + onEndDrawerChanged: onEndDrawerChanged, + floatingActionButton: floatingActionButton ?? + floatingActionButtonBuilder?.call(context, tabsRouter), + floatingActionButtonAnimator: floatingActionButtonAnimator, + floatingActionButtonLocation: floatingActionButtonLocation, + bottomSheet: bottomSheet, + persistentFooterButtons: persistentFooterButtons, + primary: primary, + appBar: appBarBuilder == null + ? null + : appBarBuilder!( + context, + tabsRouter, + ), + body: builder == null + ? FadeTransition(opacity: animation, child: child) + : builder!( + context, + child, + animation, + ), + bottomNavigationBar: bottomNavigationBuilder == null + ? null + : bottomNavigationBuilder!( + context, + tabsRouter, + ), + ); + }, + ); + } +} diff --git a/lib/src/router/widgets/custom_cupertino_transitions_builder.dart b/lib/src/router/widgets/custom_cupertino_transitions_builder.dart new file mode 100644 index 000000000..c7a574f8e --- /dev/null +++ b/lib/src/router/widgets/custom_cupertino_transitions_builder.dart @@ -0,0 +1,913 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// The adjustments made to this code is to disable unwanted shadow +// of routes when used as nested routes, e.g inside of a TabsRouter + +import 'dart:math'; +import 'dart:ui' show lerpDouble; + +import 'package:flutter/cupertino.dart' show CupertinoDynamicColor; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +const double _kBackGestureWidth = 20.0; +const double _kMinFlingVelocity = 1.0; // Screen widths per second. + +// An eyeballed value for the maximum time it takes for a page to animate forward +// if the user releases a page mid swipe. +const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds. + +// The maximum time for a page to get reset to it's original position if the +// user releases a page mid swipe. +const int _kMaxPageBackAnimationTime = 300; // Milliseconds. + +/// Barrier color for a Cupertino modal barrier. +/// +/// Extracted from https://developer.apple.com/design/resources/. +const Color kCupertinoModalBarrierColor = CupertinoDynamicColor.withBrightness( + color: Color(0x33000000), + darkColor: Color(0x7A000000), +); + +// Offset from offscreen to the right to fully on screen. +final Animatable _kRightMiddleTween = Tween( + begin: const Offset(1.0, 0.0), + end: Offset.zero, +); + +// Offset from fully on screen to 1/3 offscreen to the left. +final Animatable _kMiddleLeftTween = Tween( + begin: Offset.zero, + end: const Offset(-1.0 / 3.0, 0.0), +); + +// Offset from offscreen below to fully on screen. +final Animatable _kBottomUpTween = Tween( + begin: const Offset(0.0, 1.0), + end: Offset.zero, +); + +/// A mixin that replaces the entire screen with an iOS transition for a +/// [PageRoute]. +/// +/// {@template flutter.cupertino.cupertinoRouteTransitionMixin} +/// The page slides in from the right and exits in reverse. The page also shifts +/// to the left in parallax when another page enters to cover it. +/// +/// The page slides in from the bottom and exits in reverse with no parallax +/// effect for fullscreen dialogs. +/// {@endtemplate} +/// +/// See also: +/// +/// * [MaterialRouteTransitionMixin], which is a mixin that provides +/// platform-appropriate transitions for a [PageRoute]. +/// * [CupertinoPageRoute], which is a [PageRoute] that leverages this mixin. +mixin CustomCupertinoRouteTransitionMixin on PageRoute { + /// Builds the primary contents of the route. + @protected + Widget buildContent(BuildContext context); + + /// {@template flutter.cupertino.CupertinoRouteTransitionMixin.title} + /// A title string for this route. + /// + /// Used to auto-populate [CupertinoNavigationBar] and + /// [CupertinoSliverNavigationBar]'s `middle`/`largeTitle` widgets when + /// one is not manually supplied. + /// {@endtemplate} + String? get title; + + ValueNotifier? _previousTitle; + + /// The title string of the previous [CupertinoPageRoute]. + /// + /// The [ValueListenable]'s value is readable after the route is installed + /// onto a [Navigator]. The [ValueListenable] will also notify its listeners + /// if the value changes (such as by replacing the previous route). + /// + /// The [ValueListenable] itself will be null before the route is installed. + /// Its content value will be null if the previous route has no title or + /// is not a [CupertinoPageRoute]. + /// + /// See also: + /// + /// * [ValueListenableBuilder], which can be used to listen and rebuild + /// widgets based on a ValueListenable. + ValueListenable get previousTitle { + assert( + _previousTitle != null, + 'Cannot read the previousTitle for a route that has not yet been installed', + ); + return _previousTitle!; + } + + @override + void didChangePrevious(Route? previousRoute) { + final String? previousTitleString = + previousRoute is CustomCupertinoRouteTransitionMixin + ? previousRoute.title + : null; + if (_previousTitle == null) { + _previousTitle = ValueNotifier(previousTitleString); + } else { + _previousTitle!.value = previousTitleString; + } + super.didChangePrevious(previousRoute); + } + + @override + // A relatively rigorous eyeball estimation. + Duration get transitionDuration => const Duration(milliseconds: 400); + + @override + Color? get barrierColor => null; + + @override + String? get barrierLabel => null; + + @override + bool canTransitionTo(TransitionRoute nextRoute) { + // Don't perform outgoing animation if the next route is a fullscreen dialog. + return nextRoute is CustomCupertinoRouteTransitionMixin && + !nextRoute.fullscreenDialog; + } + + /// True if an iOS-style back swipe pop gesture is currently underway for [route]. + /// + /// This just check the route's [NavigatorState.userGestureInProgress]. + /// + /// See also: + /// + /// * [popGestureEnabled], which returns true if a user-triggered pop gesture + /// would be allowed. + static bool isPopGestureInProgress(PageRoute route) { + return route.navigator!.userGestureInProgress; + } + + /// True if an iOS-style back swipe pop gesture is currently underway for this route. + /// + /// See also: + /// + /// * [isPopGestureInProgress], which returns true if a Cupertino pop gesture + /// is currently underway for specific route. + /// * [popGestureEnabled], which returns true if a user-triggered pop gesture + /// would be allowed. + bool get popGestureInProgress => isPopGestureInProgress(this); + + /// Whether a pop gesture can be started by the user. + /// + /// Returns true if the user can edge-swipe to a previous route. + /// + /// Returns false once [isPopGestureInProgress] is true, but + /// [isPopGestureInProgress] can only become true if [popGestureEnabled] was + /// true first. + /// + /// This should only be used between frames, not during build. + bool get popGestureEnabled => _isPopGestureEnabled(this); + + static bool _isPopGestureEnabled(PageRoute route) { + // If there's nothing to go back to, then obviously we don't support + // the back gesture. + if (route.isFirst) return false; + // If the route wouldn't actually pop if we popped it, then the gesture + // would be really confusing (or would skip internal routes), so disallow it. + if (route.willHandlePopInternally) return false; + // If attempts to dismiss this route might be vetoed such as in a page + // with forms, then do not allow the user to dismiss the route with a swipe. + if (route.hasScopedWillPopCallback) return false; + // If dismiss is blocked by any PopEntries, then don't allow the user to + // dismiss the route with a swipe + if (route.popDisposition == RoutePopDisposition.doNotPop) return false; + // Fullscreen dialogs aren't dismissible by back swipe. + if (route.fullscreenDialog) return false; + // If we're in an animation already, we cannot be manually swiped. + if (route.animation!.status != AnimationStatus.completed) return false; + // If we're being popped into, we also cannot be swiped until the pop above + // it completes. This translates to our secondary animation being + // dismissed. + if (route.secondaryAnimation!.status != AnimationStatus.dismissed) { + return false; + } + // If we're in a gesture already, we cannot start another. + if (isPopGestureInProgress(route)) return false; + + // Looks like a back gesture would be welcome! + return true; + } + + @override + Widget buildPage(BuildContext context, Animation animation, + Animation secondaryAnimation) { + final Widget child = buildContent(context); + final Widget result = Semantics( + scopesRoute: true, + explicitChildNodes: true, + child: child, + ); + + return result; + } + + // Called by _CupertinoBackGestureDetector when a pop ("back") drag start + // gesture is detected. The returned controller handles all of the subsequent + // drag events. + static _CupertinoBackGestureController _startPopGesture( + PageRoute route) { + assert(_isPopGestureEnabled(route)); + + return _CupertinoBackGestureController( + navigator: route.navigator!, + controller: route.controller!, // protected access + ); + } + + /// Returns a [CupertinoFullscreenDialogTransition] if [route] is a full + /// screen dialog, otherwise a [CupertinoPageTransition] is returned. + /// + /// Used by [CupertinoPageRoute.buildTransitions]. + /// + /// This method can be applied to any [PageRoute], not just + /// [CupertinoPageRoute]. It's typically used to provide a Cupertino style + /// horizontal transition for material widgets when the target platform + /// is [TargetPlatform.iOS]. + /// + /// See also: + /// + /// * [NoShadowCupertinoPageTransitionsBuilder], which uses this method to define a + /// [PageTransitionsBuilder] for the [PageTransitionsTheme]. + static Widget buildPageTransitions( + PageRoute route, + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + // Check if the route has an animation that's currently participating + // in a back swipe gesture. + // + // In the middle of a back gesture drag, let the transition be linear to + // match finger motions. + final bool linearTransition = isPopGestureInProgress(route); + if (route.fullscreenDialog) { + return CupertinoFullscreenDialogTransition( + primaryRouteAnimation: animation, + secondaryRouteAnimation: secondaryAnimation, + linearTransition: linearTransition, + child: child, + ); + } else { + return CupertinoPageTransition( + primaryRouteAnimation: animation, + secondaryRouteAnimation: secondaryAnimation, + linearTransition: linearTransition, + child: _CupertinoBackGestureDetector( + enabledCallback: () => _isPopGestureEnabled(route), + onStartPopGesture: () => _startPopGesture(route), + child: child, + ), + ); + } + } + + @override + Widget buildTransitions(BuildContext context, Animation animation, + Animation secondaryAnimation, Widget child) { + return buildPageTransitions( + this, context, animation, secondaryAnimation, child); + } +} + +/// A modal route that replaces the entire screen with an iOS transition. +/// +/// {@macro flutter.cupertino.cupertinoRouteTransitionMixin} +/// +/// By default, when a modal route is replaced by another, the previous route +/// remains in memory. To free all the resources when this is not necessary, set +/// [maintainState] to false. +/// +/// The type `T` specifies the return type of the route which can be supplied as +/// the route is popped from the stack via [Navigator.pop] when an optional +/// `result` can be provided. +/// +/// See also: +/// +/// * [CustomCupertinoRouteTransitionMixin], for a mixin that provides iOS transition +/// for this modal route. +/// * [MaterialPageRoute], for an adaptive [PageRoute] that uses a +/// platform-appropriate transition. +/// * [CupertinoPageScaffold], for applications that have one page with a fixed +/// navigation bar on top. +/// * [CupertinoTabScaffold], for applications that have a tab bar at the +/// bottom with multiple pages. +/// * [CupertinoPage], for a [Page] version of this class. +class CupertinoPageRoute extends PageRoute + with CustomCupertinoRouteTransitionMixin { + /// Creates a page route for use in an iOS designed app. + /// + /// The [builder], [maintainState], and [fullscreenDialog] arguments must not + /// be null. + CupertinoPageRoute({ + required this.builder, + this.title, + RouteSettings? settings, + this.maintainState = true, + bool fullscreenDialog = false, + }) : super( + settings: settings, + fullscreenDialog: fullscreenDialog, + ) { + assert(opaque); + } + + /// Builds the primary contents of the route. + final WidgetBuilder builder; + + @override + Widget buildContent(BuildContext context) => builder(context); + + @override + final String? title; + + @override + final bool maintainState; + + @override + String get debugLabel => '${super.debugLabel}(${settings.name})'; +} + +/// Provides an iOS-style page transition animation. +/// +/// The page slides in from the right and exits in reverse. It also shifts to the left in +/// a parallax motion when another page enters to cover it. +class CupertinoPageTransition extends StatelessWidget { + /// Creates an iOS-style page transition. + /// + /// * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0 + /// when this screen is being pushed. + /// * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0 + /// when another screen is being pushed on top of this one. + /// * `linearTransition` is whether to perform the transitions linearly. + /// Used to precisely track back gesture drags. + CupertinoPageTransition({ + Key? key, + required Animation primaryRouteAnimation, + required Animation secondaryRouteAnimation, + required this.child, + required bool linearTransition, + }) : _primaryPositionAnimation = (linearTransition + ? primaryRouteAnimation + : CurvedAnimation( + // The curves below have been rigorously derived from plots of native + // iOS animation frames. Specifically, a video was taken of a page + // transition animation and the distance in each frame that the page + // moved was measured. A best fit bezier curve was the fitted to the + // point set, which is linearToEaseIn. Conversely, easeInToLinear is the + // reflection over the origin of linearToEaseIn. + parent: primaryRouteAnimation, + curve: Curves.linearToEaseOut, + reverseCurve: Curves.easeInToLinear, + )) + .drive(_kRightMiddleTween), + _secondaryPositionAnimation = (linearTransition + ? secondaryRouteAnimation + : CurvedAnimation( + parent: secondaryRouteAnimation, + curve: Curves.linearToEaseOut, + reverseCurve: Curves.easeInToLinear, + )) + .drive(_kMiddleLeftTween), + _primaryShadowAnimation = (linearTransition + ? primaryRouteAnimation + : CurvedAnimation( + parent: primaryRouteAnimation, + curve: Curves.linearToEaseOut, + )) + .drive(_CupertinoEdgeShadowDecoration.tween(linearTransition)), + super(key: key); + + // When this page is coming in to cover another page. + final Animation _primaryPositionAnimation; + + // When this page is becoming covered by another page. + final Animation _secondaryPositionAnimation; + final Animation _primaryShadowAnimation; + + /// The widget below this widget in the tree. + final Widget child; + + @override + Widget build(BuildContext context) { + assert(debugCheckHasDirectionality(context)); + final TextDirection textDirection = Directionality.of(context); + return SlideTransition( + position: _secondaryPositionAnimation, + textDirection: textDirection, + transformHitTests: false, + child: SlideTransition( + position: _primaryPositionAnimation, + textDirection: textDirection, + child: DecoratedBoxTransition( + decoration: _primaryShadowAnimation, + child: child, + ), + ), + ); + } +} + +/// An iOS-style transition used for summoning fullscreen dialogs. +/// +/// For example, used when creating a new calendar event by bringing in the next +/// screen from the bottom. +class CupertinoFullscreenDialogTransition extends StatelessWidget { + /// Creates an iOS-style transition used for summoning fullscreen dialogs. + /// + /// * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0 + /// when this screen is being pushed. + /// * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0 + /// when another screen is being pushed on top of this one. + /// * `linearTransition` is whether to perform the secondary transition linearly. + /// Used to precisely track back gesture drags. + CupertinoFullscreenDialogTransition({ + Key? key, + required Animation primaryRouteAnimation, + required Animation secondaryRouteAnimation, + required this.child, + required bool linearTransition, + }) : _positionAnimation = CurvedAnimation( + parent: primaryRouteAnimation, + curve: Curves.linearToEaseOut, + // The curve must be flipped so that the reverse animation doesn't play + // an ease-in curve, which iOS does not use. + reverseCurve: Curves.linearToEaseOut.flipped, + ).drive(_kBottomUpTween), + _secondaryPositionAnimation = (linearTransition + ? secondaryRouteAnimation + : CurvedAnimation( + parent: secondaryRouteAnimation, + curve: Curves.linearToEaseOut, + reverseCurve: Curves.easeInToLinear, + )) + .drive(_kMiddleLeftTween), + super(key: key); + + final Animation _positionAnimation; + + // When this page is becoming covered by another page. + final Animation _secondaryPositionAnimation; + + /// The widget below this widget in the tree. + final Widget child; + + @override + Widget build(BuildContext context) { + assert(debugCheckHasDirectionality(context)); + final TextDirection textDirection = Directionality.of(context); + return SlideTransition( + position: _secondaryPositionAnimation, + textDirection: textDirection, + transformHitTests: false, + child: SlideTransition( + position: _positionAnimation, + child: child, + ), + ); + } +} + +/// This is the widget side of [_CupertinoBackGestureController]. +/// +/// This widget provides a gesture recognizer which, when it determines the +/// route can be closed with a back gesture, creates the controller and +/// feeds it the input from the gesture recognizer. +/// +/// The gesture data is converted from absolute coordinates to logical +/// coordinates by this widget. +/// +/// The type `T` specifies the return type of the route with which this gesture +/// detector is associated. +class _CupertinoBackGestureDetector extends StatefulWidget { + const _CupertinoBackGestureDetector({ + Key? key, + required this.enabledCallback, + required this.onStartPopGesture, + required this.child, + }) : super(key: key); + + final Widget child; + + final ValueGetter enabledCallback; + + final ValueGetter<_CupertinoBackGestureController> onStartPopGesture; + + @override + _CupertinoBackGestureDetectorState createState() => + _CupertinoBackGestureDetectorState(); +} + +class _CupertinoBackGestureDetectorState + extends State<_CupertinoBackGestureDetector> { + _CupertinoBackGestureController? _backGestureController; + + late HorizontalDragGestureRecognizer _recognizer; + + @override + void initState() { + super.initState(); + _recognizer = HorizontalDragGestureRecognizer(debugOwner: this) + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..onCancel = _handleDragCancel; + } + + @override + void dispose() { + _recognizer.dispose(); + super.dispose(); + } + + void _handleDragStart(DragStartDetails details) { + assert(mounted); + assert(_backGestureController == null); + _backGestureController = widget.onStartPopGesture(); + } + + void _handleDragUpdate(DragUpdateDetails details) { + assert(mounted); + assert(_backGestureController != null); + _backGestureController!.dragUpdate( + _convertToLogical(details.primaryDelta! / context.size!.width)); + } + + void _handleDragEnd(DragEndDetails details) { + assert(mounted); + assert(_backGestureController != null); + _backGestureController!.dragEnd(_convertToLogical( + details.velocity.pixelsPerSecond.dx / context.size!.width)); + _backGestureController = null; + } + + void _handleDragCancel() { + assert(mounted); + // This can be called even if start is not called, paired with the "down" event + // that we don't consider here. + _backGestureController?.dragEnd(0.0); + _backGestureController = null; + } + + void _handlePointerDown(PointerDownEvent event) { + if (widget.enabledCallback()) _recognizer.addPointer(event); + } + + double _convertToLogical(double value) { + switch (Directionality.of(context)) { + case TextDirection.rtl: + return -value; + case TextDirection.ltr: + return value; + } + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasDirectionality(context)); + // For devices with notches, the drag area needs to be larger on the side + // that has the notch. + double dragAreaWidth = Directionality.of(context) == TextDirection.ltr + ? MediaQuery.of(context).padding.left + : MediaQuery.of(context).padding.right; + dragAreaWidth = max(dragAreaWidth, _kBackGestureWidth); + return Stack( + fit: StackFit.passthrough, + children: [ + widget.child, + PositionedDirectional( + start: 0.0, + width: dragAreaWidth, + top: 0.0, + bottom: 0.0, + child: Listener( + onPointerDown: _handlePointerDown, + behavior: HitTestBehavior.translucent, + ), + ), + ], + ); + } +} + +/// A controller for an iOS-style back gesture. +/// +/// This is created by a [CupertinoPageRoute] in response from a gesture caught +/// by a [_CupertinoBackGestureDetector] widget, which then also feeds it input +/// from the gesture. It controls the animation controller owned by the route, +/// based on the input provided by the gesture detector. +/// +/// This class works entirely in logical coordinates (0.0 is new page dismissed, +/// 1.0 is new page on top). +/// +/// The type `T` specifies the return type of the route with which this gesture +/// detector controller is associated. +class _CupertinoBackGestureController { + /// Creates a controller for an iOS-style back gesture. + /// + /// The [navigator] and [controller] arguments must not be null. + _CupertinoBackGestureController({ + required this.navigator, + required this.controller, + }) { + navigator.didStartUserGesture(); + } + + final AnimationController controller; + final NavigatorState navigator; + + /// The drag gesture has changed by [fractionalDelta]. The total range of the + /// drag should be 0.0 to 1.0. + void dragUpdate(double delta) { + controller.value -= delta; + } + + /// The drag gesture has ended with a horizontal motion of + /// [fractionalVelocity] as a fraction of screen width per second. + void dragEnd(double velocity) { + // Fling in the appropriate direction. + // AnimationController.fling is guaranteed to + // take at least one frame. + // + // This curve has been determined through rigorously eyeballing native iOS + // animations. + const Curve animationCurve = Curves.fastLinearToSlowEaseIn; + final bool animateForward; + + // If the user releases the page before mid screen with sufficient velocity, + // or after mid screen, we should animate the page out. Otherwise, the page + // should be animated back in. + if (velocity.abs() >= _kMinFlingVelocity) { + animateForward = velocity <= 0; + } else { + animateForward = controller.value > 0.5; + } + + if (animateForward) { + // The closer the panel is to dismissing, the shorter the animation is. + // We want to cap the animation time, but we want to use a linear curve + // to determine it. + final int droppedPageForwardAnimationTime = min( + lerpDouble( + _kMaxDroppedSwipePageForwardAnimationTime, 0, controller.value)! + .floor(), + _kMaxPageBackAnimationTime, + ); + controller.animateTo(1.0, + duration: Duration(milliseconds: droppedPageForwardAnimationTime), + curve: animationCurve); + } else { + // This route is destined to pop at this point. Reuse navigator's pop. + navigator.pop(); + + // The popping may have finished inline if already at the target destination. + if (controller.isAnimating) { + // Otherwise, use a custom popping animation duration and curve. + final int droppedPageBackAnimationTime = lerpDouble( + 0, _kMaxDroppedSwipePageForwardAnimationTime, controller.value)! + .floor(); + controller.animateBack(0.0, + duration: Duration(milliseconds: droppedPageBackAnimationTime), + curve: animationCurve); + } + } + + if (controller.isAnimating) { + // Keep the userGestureInProgress in true state so we don't change the + // curve of the page transition mid-flight since CupertinoPageTransition + // depends on userGestureInProgress. + late AnimationStatusListener animationStatusCallback; + animationStatusCallback = (AnimationStatus status) { + navigator.didStopUserGesture(); + controller.removeStatusListener(animationStatusCallback); + }; + controller.addStatusListener(animationStatusCallback); + } else { + navigator.didStopUserGesture(); + } + } +} + +// A custom [Decoration] used to paint an extra shadow on the start edge of the +// box it's decorating. It's like a [BoxDecoration] with only a gradient except +// it paints on the start side of the box instead of behind the box. +class _CupertinoEdgeShadowDecoration extends Decoration { + const _CupertinoEdgeShadowDecoration._([this._colors]); + + static DecorationTween tween(bool withShadow) => DecorationTween( + begin: const _CupertinoEdgeShadowDecoration + ._(), // No decoration initially. + end: _CupertinoEdgeShadowDecoration._( + // Eyeballed gradient used to mimic a drop shadow on the start side only. + withShadow + ? const [ + Color(0x38000000), + Color(0x12000000), + Color(0x04000000), + Color(0x00000000), + ] + : const [], + ), + ); + + // Colors used to paint a gradient at the start edge of the box it is + // decorating. + // + // The first color in the list is used at the start of the gradient, which + // is located at the start edge of the decorated box. + // + // If this is null, no shadow is drawn. + // + // The list must have at least two colors in it (otherwise it would not be a + // gradient). + final List? _colors; + + // Linearly interpolate between two edge shadow decorations decorations. + // + // The `t` argument represents position on the timeline, with 0.0 meaning + // that the interpolation has not started, returning `a` (or something + // equivalent to `a`), 1.0 meaning that the interpolation has finished, + // returning `b` (or something equivalent to `b`), and values in between + // meaning that the interpolation is at the relevant point on the timeline + // between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and + // 1.0, so negative values and values greater than 1.0 are valid (and can + // easily be generated by curves such as [Curves.elasticInOut]). + // + // Values for `t` are usually obtained from an [Animation], such as + // an [AnimationController]. + // + // See also: + // + // * [Decoration.lerp]. + static _CupertinoEdgeShadowDecoration? lerp( + _CupertinoEdgeShadowDecoration? a, + _CupertinoEdgeShadowDecoration? b, + double t, + ) { + if (a == null && b == null) return null; + if (a == null) { + return b!._colors == null + ? b + : _CupertinoEdgeShadowDecoration._(b._colors! + .map((Color color) => Color.lerp(null, color, t)!) + .toList()); + } + if (b == null) { + return a._colors == null + ? a + : _CupertinoEdgeShadowDecoration._(a._colors! + .map((Color color) => Color.lerp(null, color, 1.0 - t)!) + .toList()); + } + assert(b._colors != null || a._colors != null); + // If it ever becomes necessary, we could allow decorations with different + // length' here, similarly to how it is handled in [LinearGradient.lerp]. + assert(b._colors == null || + a._colors == null || + a._colors!.length == b._colors!.length); + return _CupertinoEdgeShadowDecoration._( + [ + for (int i = 0; i < b._colors!.length; i += 1) + Color.lerp(a._colors?[i], b._colors?[i], t)!, + ], + ); + } + + @override + _CupertinoEdgeShadowDecoration lerpFrom(Decoration? a, double t) { + if (a is _CupertinoEdgeShadowDecoration) { + return _CupertinoEdgeShadowDecoration.lerp(a, this, t)!; + } + return _CupertinoEdgeShadowDecoration.lerp(null, this, t)!; + } + + @override + _CupertinoEdgeShadowDecoration lerpTo(Decoration? b, double t) { + if (b is _CupertinoEdgeShadowDecoration) { + return _CupertinoEdgeShadowDecoration.lerp(this, b, t)!; + } + return _CupertinoEdgeShadowDecoration.lerp(this, null, t)!; + } + + @override + _CupertinoEdgeShadowPainter createBoxPainter([VoidCallback? onChanged]) { + return _CupertinoEdgeShadowPainter(this, onChanged); + } + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) return false; + return other is _CupertinoEdgeShadowDecoration && other._colors == _colors; + } + + @override + int get hashCode => _colors.hashCode; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(IterableProperty('colors', _colors)); + } +} + +/// A [BoxPainter] used to draw the page transition shadow using gradients. +class _CupertinoEdgeShadowPainter extends BoxPainter { + _CupertinoEdgeShadowPainter( + this._decoration, + VoidCallback? onChange, + ) : super(onChange); + + final _CupertinoEdgeShadowDecoration _decoration; + + @override + void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { + final List? colors = _decoration._colors; + if (colors == null || colors.isEmpty) { + return; + } + + // The following code simulates drawing a [LinearGradient] configured as + // follows: + // + // LinearGradient( + // begin: AlignmentDirectional(0.90, 0.0), // Spans 5% of the page. + // colors: _decoration._colors, + // ) + // + // A performance evaluation on Feb 8, 2021 showed, that drawing the gradient + // manually as implemented below is more performant than relying on + // [LinearGradient.createShader] because compiling that shader takes a long + // time. On an iPhone XR, the implementation below reduced the worst frame + // time for a cupertino page transition of a newly installed app from ~95ms + // down to ~30ms, mainly because there's no longer a need to compile a + // shader for the LinearGradient. + // + // The implementation below divides the width of the shadow into multiple + // bands of equal width, one for each color interval defined by + // `_decoration._colors`. Band x is filled with a gradient going from + // `_decoration._colors[x]` to `_decoration._colors[x + 1]` by drawing a + // bunch of 1px wide rects. The rects change their color by lerping between + // the two colors that define the interval of the band. + + // Shadow spans 5% of the page. + final double shadowWidth = 0.05 * configuration.size!.width; + final double shadowHeight = configuration.size!.height; + final double bandWidth = shadowWidth / (colors.length - 1); + + final TextDirection? textDirection = configuration.textDirection; + assert(textDirection != null); + final double start; + final double shadowDirection; // -1 for ltr, 1 for rtl. + switch (textDirection!) { + case TextDirection.rtl: + start = offset.dx + configuration.size!.width; + shadowDirection = 1; + break; + case TextDirection.ltr: + start = offset.dx; + shadowDirection = -1; + break; + } + + int bandColorIndex = 0; + for (int dx = 0; dx < shadowWidth; dx += 1) { + if (dx ~/ bandWidth != bandColorIndex) { + bandColorIndex += 1; + } + final Paint paint = Paint() + ..color = Color.lerp(colors[bandColorIndex], colors[bandColorIndex + 1], + (dx % bandWidth) / bandWidth)!; + final double x = start + shadowDirection * dx; + canvas.drawRect( + Rect.fromLTWH(x - 1.0, offset.dy, 1.0, shadowHeight), paint); + } + } +} + +class NoShadowCupertinoPageTransitionsBuilder extends PageTransitionsBuilder { + /// Constructs a page transition animation that matches the iOS transition. + const NoShadowCupertinoPageTransitionsBuilder(); + + @override + Widget buildTransitions( + PageRoute route, + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + return CustomCupertinoRouteTransitionMixin.buildPageTransitions( + route, context, animation, secondaryAnimation, child); + } +} diff --git a/lib/src/router/widgets/deferred_widget.dart b/lib/src/router/widgets/deferred_widget.dart new file mode 100644 index 000000000..4be0caedd --- /dev/null +++ b/lib/src/router/widgets/deferred_widget.dart @@ -0,0 +1,96 @@ +// Copyright 2019 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'package:flutter/material.dart'; + +typedef LibraryLoader = Future Function(); +typedef DeferredWidgetBuilder = Widget Function(); + +/// Wraps the child inside a deferred module loader. +/// +/// The child is created and a single instance of the Widget is maintained in +/// state as long as closure to create widget stays the same. +/// +class DeferredWidget extends StatefulWidget { + DeferredWidget( + this.libraryLoader, + this.createWidget, { + super.key, + Widget? placeholder, + }) : placeholder = placeholder ?? Container(); + + final LibraryLoader libraryLoader; + final DeferredWidgetBuilder createWidget; + final Widget placeholder; + static final Map> _moduleLoaders = {}; + static final Set _loadedModules = {}; + + static Future preload(LibraryLoader loader) { + if (!_moduleLoaders.containsKey(loader)) { + _moduleLoaders[loader] = loader().then((dynamic _) { + _loadedModules.add(loader); + }); + } + return _moduleLoaders[loader]!; + } + + @override + State createState() => _DeferredWidgetState(); +} + +class _DeferredWidgetState extends State { + _DeferredWidgetState(); + + Widget? _loadedChild; + DeferredWidgetBuilder? _loadedCreator; + + @override + void initState() { + /// If module was already loaded immediately create widget instead of + /// waiting for future or zone turn. + if (DeferredWidget._loadedModules.contains(widget.libraryLoader)) { + _onLibraryLoaded(); + } else { + DeferredWidget.preload(widget.libraryLoader) + .then((dynamic _) => _onLibraryLoaded()); + } + super.initState(); + } + + void _onLibraryLoaded() { + setState(() { + _loadedCreator = widget.createWidget; + _loadedChild = _loadedCreator!(); + }); + } + + @override + Widget build(BuildContext context) { + /// If closure to create widget changed, create new instance, otherwise + /// treat as const Widget. + if (_loadedCreator != widget.createWidget && _loadedCreator != null) { + _loadedCreator = widget.createWidget; + _loadedChild = _loadedCreator!(); + } + return _loadedChild ?? widget.placeholder; + } +} + +/// Displays a progress indicator when the widget is a deferred component +/// and is currently being installed. +class DeferredLoadingPlaceholder extends StatelessWidget { + const DeferredLoadingPlaceholder({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); + } +} diff --git a/lib/src/router/widgets/empty_router_widgets.dart b/lib/src/router/widgets/empty_router_widgets.dart new file mode 100644 index 000000000..3520e8e06 --- /dev/null +++ b/lib/src/router/widgets/empty_router_widgets.dart @@ -0,0 +1,10 @@ +import 'package:flutter/widgets.dart'; +import 'package:stacked/src/router/widgets/nested_router.dart'; + +class EmptyRouterPage extends NestedRouter { + const EmptyRouterPage({Key? key}) : super(key: key); +} + +class EmptyRouterScreen extends NestedRouter { + const EmptyRouterScreen({Key? key}) : super(key: key); +} diff --git a/lib/src/router/widgets/nested_router.dart b/lib/src/router/widgets/nested_router.dart new file mode 100644 index 000000000..66457b630 --- /dev/null +++ b/lib/src/router/widgets/nested_router.dart @@ -0,0 +1,261 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/src/router/controller/controller_scope.dart'; +import 'package:stacked/src/router/controller/routing_controller.dart'; +import 'package:stacked/src/router/route/page_route_info.dart'; +import 'package:stacked/src/router/widgets/route_navigator.dart'; + +class NestedRouter extends StatefulWidget { + final NavigatorObserversBuilder navigatorObservers; + final Widget Function(BuildContext context, Widget content)? builder; + final String? navRestorationScopeId; + final bool inheritNavigatorObservers; + final GlobalKey? navigatorKey; + final WidgetBuilder? placeholder; + + const NestedRouter({ + Key? key, + this.navigatorObservers = + NestedRouterDelegate.defaultNavigatorObserversBuilder, + this.builder, + this.navRestorationScopeId, + this.navigatorKey, + this.inheritNavigatorObservers = true, + this.placeholder, + }) : super(key: key); + + static Widget declarative({ + Key? key, + NavigatorObserversBuilder navigatorObservers = + NestedRouterDelegate.defaultNavigatorObserversBuilder, + required RoutesBuilder routes, + RoutePopCallBack? onPopRoute, + String? navRestorationScopeId, + bool inheritNavigatorObservers = true, + GlobalKey? navigatorKey, + OnNestedNavigateCallBack? onNavigate, + WidgetBuilder? placeholder, + }) => + _DeclarativeStackedRouter( + onPopRoute: onPopRoute, + navigatorKey: navigatorKey, + navRestorationScopeId: navRestorationScopeId, + navigatorObservers: navigatorObservers, + inheritNavigatorObservers: inheritNavigatorObservers, + onNavigate: onNavigate, + placeholder: placeholder, + routes: routes, + ); + + @override + NestedRouterState createState() => NestedRouterState(); + + static StackRouter of(BuildContext context, {bool watch = false}) { + var scope = StackRouterScope.of(context, watch: watch); + assert(() { + if (scope == null) { + throw FlutterError( + 'NestedRouter operation requested with a context that does not include an NestedRouter.\n' + 'The context used to retrieve the Router must be that of a widget that ' + 'is a descendant of an NestedRouter widget.'); + } + return true; + }()); + return scope!.controller; + } + + static StackRouter? innerRouterOf(BuildContext context, String routeName) { + return of(context).innerRouterOf(routeName); + } +} + +class NestedRouterState extends State { + StackRouter? _controller; + + StackRouter? get controller => _controller; + late List _navigatorObservers; + late NavigatorObserversBuilder _inheritableObserversBuilder; + late RoutingController _parentController; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (_controller == null) { + final parentRouteData = RouteData.of(context); + final parentScope = RouterScope.of(context, watch: true); + _inheritableObserversBuilder = () { + var observers = widget.navigatorObservers(); + if (!widget.inheritNavigatorObservers) { + return observers; + } + var inheritedObservers = parentScope.inheritableObserversBuilder(); + return inheritedObservers + observers; + }; + _navigatorObservers = _inheritableObserversBuilder(); + _parentController = parentScope.controller; + _controller = NestedStackRouter( + parent: _parentController, + key: parentRouteData.key, + routeData: parentRouteData, + navigatorKey: widget.navigatorKey, + routeCollection: _parentController.routeCollection.subCollectionOf( + parentRouteData.name, + ), + pageBuilder: _parentController.pageBuilder, + ); + + _parentController.attachChildController(_controller!); + _controller!.addListener(_rebuildListener); + } + } + + void _rebuildListener() { + if (mounted) { + setState(() {}); + } + } + + @override + Widget build(BuildContext context) { + assert(_controller != null); + var navigator = RouteNavigator( + router: _controller!, + navRestorationScopeId: widget.navRestorationScopeId, + navigatorObservers: _navigatorObservers, + placeholder: widget.placeholder, + ); + final stateHash = controller!.stateHash; + return RouterScope( + controller: _controller!, + inheritableObserversBuilder: _inheritableObserversBuilder, + navigatorObservers: _navigatorObservers, + stateHash: stateHash, + child: StackRouterScope( + controller: _controller!, + stateHash: stateHash, + child: widget.builder == null + ? navigator + : Builder( + builder: (ctx) => widget.builder!(ctx, navigator), + ), + ), + ); + } + + @override + void dispose() { + super.dispose(); + if (_controller != null) { + _controller!.removeListener(_rebuildListener); + _controller!.dispose(); + _parentController.removeChildController(_controller!); + _controller = null; + } + } +} + +typedef RoutesGenerator = List Function( + BuildContext context, List routes); + +class _DeclarativeStackedRouter extends StatefulWidget { + final RoutesBuilder routes; + final RoutePopCallBack? onPopRoute; + final NavigatorObserversBuilder navigatorObservers; + final String? navRestorationScopeId; + final bool inheritNavigatorObservers; + final GlobalKey? navigatorKey; + final OnNestedNavigateCallBack? onNavigate; + final WidgetBuilder? placeholder; + + const _DeclarativeStackedRouter({ + Key? key, + required this.routes, + this.navigatorObservers = + NestedRouterDelegate.defaultNavigatorObserversBuilder, + this.onPopRoute, + this.navigatorKey, + this.navRestorationScopeId, + this.inheritNavigatorObservers = true, + this.onNavigate, + this.placeholder, + }) : super(key: key); + + @override + _DeclarativeStackedRouterState createState() => + _DeclarativeStackedRouterState(); +} + +class _DeclarativeStackedRouterState extends State<_DeclarativeStackedRouter> { + StackRouter? _controller; + late HeroController _heroController; + + StackRouter? get controller => _controller; + late List _navigatorObservers; + late NavigatorObserversBuilder _inheritableObserversBuilder; + late RoutingController _parentController; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final parentData = RouteData.of(context); + if (_controller == null) { + _heroController = HeroController(); + final parentScope = RouterScope.of(context); + _inheritableObserversBuilder = () { + var observers = widget.navigatorObservers(); + if (!widget.inheritNavigatorObservers) { + return observers; + } + var inheritedObservers = parentScope.inheritableObserversBuilder(); + return inheritedObservers + observers; + }; + _navigatorObservers = _inheritableObserversBuilder(); + _parentController = parentScope.controller; + _controller = NestedStackRouter( + parent: _parentController, + key: parentData.key, + routeData: parentData, + managedByWidget: true, + onNavigate: widget.onNavigate, + navigatorKey: widget.navigatorKey, + routeCollection: _parentController.routeCollection.subCollectionOf( + parentData.name, + ), + pageBuilder: _parentController.pageBuilder); + _parentController.attachChildController(_controller!); + } + } + + @override + void dispose() { + super.dispose(); + if (_controller != null) { + _controller!.dispose(); + _parentController.removeChildController(_controller!); + _controller = null; + } + } + + @override + Widget build(BuildContext context) { + assert(_controller != null); + final stateHash = controller!.stateHash; + + return RouterScope( + controller: _controller!, + inheritableObserversBuilder: _inheritableObserversBuilder, + navigatorObservers: _navigatorObservers, + stateHash: stateHash, + child: HeroControllerScope( + controller: _heroController, + child: RouteNavigator( + router: _controller!, + declarativeRoutesBuilder: widget.routes, + navRestorationScopeId: widget.navRestorationScopeId, + navigatorObservers: _navigatorObservers, + didPop: widget.onPopRoute, + placeholder: widget.placeholder, + ), + ), + ); + } +} diff --git a/lib/src/router/widgets/route_navigator.dart b/lib/src/router/widgets/route_navigator.dart new file mode 100644 index 000000000..8eaaa7fba --- /dev/null +++ b/lib/src/router/widgets/route_navigator.dart @@ -0,0 +1,103 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/src/router/controller/routing_controller.dart'; +import 'package:stacked/src/router/route/page_route_info.dart'; +import 'package:stacked/src/router/stacked_page.dart'; + +class RouteNavigator extends StatefulWidget { + final StackRouter router; + final String? navRestorationScopeId; + final WidgetBuilder? placeholder; + final List navigatorObservers; + final RoutePopCallBack? didPop; + final RoutesBuilder? declarativeRoutesBuilder; + + const RouteNavigator({ + required this.router, + required this.navigatorObservers, + this.navRestorationScopeId, + this.didPop, + this.declarativeRoutesBuilder, + this.placeholder, + Key? key, + }) : super(key: key); + + @override + RouteNavigatorState createState() => RouteNavigatorState(); +} + +class RouteNavigatorState extends State { + List? _routesSnapshot; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (widget.declarativeRoutesBuilder != null && _routesSnapshot == null) { + _updateDeclarativeRoutes(); + } + } + + void _updateDeclarativeRoutes() { + final delegate = NestedRouterDelegate.of(context); + var newRoutes = + widget.declarativeRoutesBuilder!(widget.router.pendingRoutesHandler); + if (!const ListEquality().equals(newRoutes, _routesSnapshot)) { + _routesSnapshot = newRoutes; + widget.router.updateDeclarativeRoutes(newRoutes); + WidgetsBinding.instance.addPostFrameCallback((_) { + delegate.notifyUrlChanged(); + }); + } + } + + @override + void didUpdateWidget(covariant RouteNavigator oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.declarativeRoutesBuilder != null) { + _updateDeclarativeRoutes(); + } + } + + @override + Widget build(BuildContext context) { + final navigator = widget.router.hasEntries + ? Navigator( + key: widget.router.navigatorKey, + observers: [ + widget.router.pagelessRoutesObserver, + ...widget.navigatorObservers + ], + restorationScopeId: + widget.navRestorationScopeId ?? widget.router.routeData.name, + pages: widget.router.stack, + onPopPage: (route, result) { + if (!route.didPop(result)) { + return false; + } + if (route.settings is StackedPage) { + var routeData = (route.settings as StackedPage).routeData; + widget.router.removeRoute(routeData); + widget.didPop?.call(routeData.route, result); + } + return true; + }, + ) + : widget.placeholder?.call(context) ?? + Container( + color: Theme.of(context).scaffoldBackgroundColor, + ); + + // fixes nested cupertino routes back gesture issue + if (!widget.router.isRoot) { + return WillPopScope( + onWillPop: widget.router.canPop(ignoreParentRoutes: true) + ? () => SynchronousFuture(true) + : null, + child: navigator, + ); + } + + return navigator; + } +} diff --git a/lib/src/router/widgets/stacked_leading_button.dart b/lib/src/router/widgets/stacked_leading_button.dart new file mode 100644 index 000000000..ea3fbf465 --- /dev/null +++ b/lib/src/router/widgets/stacked_leading_button.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/src/router/controller/controller_scope.dart'; +import 'package:stacked/src/router/controller/pageless_routes_observer.dart'; +import 'package:stacked/src/router/widgets/nested_router.dart'; + +enum LeadingType { + back, + close, + drawer, + noLeading; + + bool get isBack => this == back; + + bool get isClose => this == close; + + bool get isDrawer => this == drawer; + + bool get isNoLeading => this == noLeading; +} + +typedef LeadingButtonBuilder = Widget Function( + BuildContext context, + LeadingType leadingType, + VoidCallback? action, // could be popTop, openDrawer or null +); + +class StackedLeadingButton extends StatefulWidget { + final Color? color; + + final bool showIfChildCanPop, ignorePagelessRoutes; + final bool _showIfParentCanPop; + final LeadingButtonBuilder? builder; + + const StackedLeadingButton({ + Key? key, + this.color, + @Deprecated('Use showIfParentCanPop') bool? showBackIfParentCanPop, + bool? showIfParentCanPop, + this.showIfChildCanPop = true, + this.ignorePagelessRoutes = false, + this.builder, + }) : assert(color == null || builder == null), + _showIfParentCanPop = + showIfParentCanPop ?? showBackIfParentCanPop ?? true, + super(key: key); + + @override + State createState() => _StackedLeadingButtonState(); +} + +class _StackedLeadingButtonState extends State { + late final PagelessRoutesObserver _pagelessRoutesObserver; + + @override + void initState() { + super.initState(); + _pagelessRoutesObserver = NestedRouter.of(context).pagelessRoutesObserver; + _pagelessRoutesObserver.addListener(_handleRebuild); + } + + @override + void dispose() { + super.dispose(); + _pagelessRoutesObserver.removeListener(_handleRebuild); + } + + @override + Widget build(BuildContext context) { + final scope = RouterScope.of(context, watch: true); + if (scope.controller.canPop( + ignoreChildRoutes: !widget.showIfChildCanPop, + ignoreParentRoutes: !widget._showIfParentCanPop, + ignorePagelessRoutes: widget.ignorePagelessRoutes, + )) { + final topPage = scope.controller.topPage; + final bool useCloseButton = topPage?.fullscreenDialog ?? false; + + if (widget.builder != null) { + return widget.builder!( + context, + useCloseButton ? LeadingType.close : LeadingType.back, + scope.controller.popTop, + ); + } + return useCloseButton + ? CloseButton( + color: widget.color, + onPressed: scope.controller.popTop, + ) + : BackButton( + color: widget.color, + onPressed: scope.controller.popTop, + ); + } + final ScaffoldState? scaffold = Scaffold.maybeOf(context); + if (scaffold?.hasDrawer == true) { + if (widget.builder != null) { + return widget.builder!( + context, + LeadingType.drawer, + _handleDrawerButton, + ); + } + return IconButton( + icon: const Icon(Icons.menu), + iconSize: Theme.of(context).iconTheme.size ?? 24, + onPressed: _handleDrawerButton, + tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip, + ); + } + + if (widget.builder != null) { + return widget.builder!(context, LeadingType.noLeading, null); + } + return const SizedBox.shrink(); + } + + void _handleDrawerButton() { + Scaffold.of(context).openDrawer(); + } + + void _handleRebuild() { + setState(() {}); + } +} diff --git a/lib/src/router/widgets/stacked_page_view.dart b/lib/src/router/widgets/stacked_page_view.dart new file mode 100644 index 000000000..f89a654e9 --- /dev/null +++ b/lib/src/router/widgets/stacked_page_view.dart @@ -0,0 +1,150 @@ +/// Most of the code here is taking from flutter's [TabView] +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/src/router/controller/routing_controller.dart'; +import 'package:stacked/src/router/widgets/stacked_tabs_router.dart'; + +class StackedPageView extends StatefulWidget { + const StackedPageView({ + Key? key, + required this.controller, + this.physics, + required this.router, + this.dragStartBehavior = DragStartBehavior.start, + this.scrollDirection = Axis.horizontal, + }) : super(key: key); + + final PageController controller; + final Axis scrollDirection; + final TabsRouter router; + + /// How the page view should respond to user input. + /// + /// For example, determines how the page view continues to animate after the + /// user stops dragging the page view. + /// + /// The physics are modified to snap to page boundaries using + /// [PageScrollPhysics] prior to being used. + /// + /// Defaults to matching platform conventions. + final ScrollPhysics? physics; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + @override + State createState() => StackedPageViewState(); +} + +class StackedPageViewState extends State { + late final PageController _controller = widget.controller; + late final TabsRouter _router = widget.router; + late List _children; + int _warpUnderwayCount = 0; + + @override + void initState() { + super.initState(); + _updateChildren(); + _router.addListener(_routerListener); + } + + @override + void didUpdateWidget(StackedPageView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) { + _controller.jumpToPage(_router.activeIndex); + } + } + + @override + void dispose() { + _router.removeListener(_routerListener); + super.dispose(); + } + + void _routerListener() { + _updateChildren(); + if (_router.activeIndex != _controller.page!.round()) { + _warpToCurrentIndex(); + } + } + + void _updateChildren() { + final stack = widget.router.stack; + _children = List.generate( + stack.length, + (index) => KeepAliveTab( + key: ValueKey(index), + page: stack[index], + ), + ); + } + + Future _warpToCurrentIndex() async { + if (!mounted) return Future.value(); + + const Duration duration = Duration(milliseconds: 300); + + if (duration == Duration.zero) { + _controller.jumpToPage(_router.activeIndex); + return Future.value(); + } + final int previousIndex = _router.previousIndex ?? 0; + if ((_router.activeIndex - previousIndex).abs() == 1) { + _warpUnderwayCount += 1; + await _controller.animateToPage(_router.activeIndex, + duration: duration, curve: Curves.ease); + _warpUnderwayCount -= 1; + return Future.value(); + } + assert((_router.activeIndex - previousIndex).abs() > 1); + final int initialPage = _router.activeIndex > previousIndex + ? _router.activeIndex - 1 + : _router.activeIndex + 1; + + setState(() { + _warpUnderwayCount += 1; + _children = List.of(_children, growable: false); + final Widget temp = _children[initialPage]; + _children[initialPage] = _children[previousIndex]; + _children[previousIndex] = temp; + }); + _controller.jumpToPage(initialPage); + + await _controller.animateToPage(_router.activeIndex, + duration: duration, curve: Curves.ease); + if (!mounted) return Future.value(); + setState(() { + _warpUnderwayCount -= 1; + }); + } + + // Called when the PageView scrolls + bool _handleScrollNotification(ScrollNotification notification) { + if (_warpUnderwayCount > 0) return false; + if (notification.depth != 0) return false; + _warpUnderwayCount += 1; + if (notification is ScrollUpdateNotification) { + _router.setActiveIndex(_controller.page!.round()); + } + _warpUnderwayCount -= 1; + return false; + } + + @override + Widget build(BuildContext context) { + return NotificationListener( + onNotification: _handleScrollNotification, + child: PageView( + scrollDirection: widget.scrollDirection, + dragStartBehavior: widget.dragStartBehavior, + controller: _controller, + physics: widget.physics == null + ? const PageScrollPhysics().applyTo(const ClampingScrollPhysics()) + : const PageScrollPhysics().applyTo(widget.physics), + children: _children, + ), + ); + } +} diff --git a/lib/src/router/widgets/stacked_tab_view.dart b/lib/src/router/widgets/stacked_tab_view.dart new file mode 100644 index 000000000..a88ed846a --- /dev/null +++ b/lib/src/router/widgets/stacked_tab_view.dart @@ -0,0 +1,209 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// The adjustments made to this code is to fix children not +/// updating in sync with TabRouter changes +/// and to set pageController.offset.round() to [TabController.index] +/// so page is set when the scroll pos is rounded to it +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/src/router/controller/routing_controller.dart'; +import 'package:stacked/src/router/widgets/stacked_tabs_router.dart'; + +class StackedTabView extends StatefulWidget { + /// Creates a page view with one child per tab. + + const StackedTabView({ + Key? key, + required this.controller, + this.physics, + required this.router, + this.scrollDirection = Axis.horizontal, + this.dragStartBehavior = DragStartBehavior.start, + }) : super(key: key); + final Axis scrollDirection; + final TabController controller; + + final TabsRouter router; + + /// How the page view should respond to user input. + /// + /// For example, determines how the page view continues to animate after the + /// user stops dragging the page view. + /// + /// The physics are modified to snap to page boundaries using + /// [PageScrollPhysics] prior to being used. + /// + /// Defaults to matching platform conventions. + final ScrollPhysics? physics; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + @override + State createState() => StackedTabViewState(); +} + +class StackedTabViewState extends State { + TabController get _controller => widget.controller; + late PageController _pageController; + late List _children; + int? _currentIndex; + int _warpUnderwayCount = 0; + + TabsRouter get _router => widget.router; + + // If the TabBarView is rebuilt with a new tab controller, the caller should + // dispose the old one. In that case the old controller's animation will be + // null and should not be accessed. + bool get _controllerIsValid => _controller.animation != null; + + @override + void initState() { + super.initState(); + _updateChildren(); + _controller.animation!.addListener(_handleTabControllerAnimationTick); + _router.addListener(_updateChildren); + _pageController = PageController(initialPage: _router.activeIndex); + } + + @override + void didUpdateWidget(StackedTabView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) { + _currentIndex = _controller.index; + _pageController.jumpToPage(_currentIndex!); + } + } + + @override + void dispose() { + if (_controllerIsValid) { + _controller.animation!.removeListener(_handleTabControllerAnimationTick); + } + _router.removeListener(_updateChildren); + super.dispose(); + } + + void _updateChildren() { + final stack = _router.stack; + _children = List.generate( + stack.length, + (index) => KeepAliveTab( + key: ValueKey(index), + page: stack[index], + ), + ); + } + + void _handleTabControllerAnimationTick() { + if (_warpUnderwayCount > 0 || !_controller.indexIsChanging) { + return; + } // This widget is driving the controller's animation. + + if (_controller.index != _currentIndex) { + _currentIndex = _controller.index; + _warpToCurrentIndex(); + } + } + + Future _warpToCurrentIndex() async { + if (!mounted) return Future.value(); + + if (_pageController.page == _currentIndex!.toDouble()) { + return Future.value(); + } + + final Duration duration = _controller.animationDuration; + + if (duration == Duration.zero) { + _pageController.jumpToPage(_currentIndex!); + return Future.value(); + } + + final int previousIndex = _controller.previousIndex; + + if ((_currentIndex! - previousIndex).abs() == 1) { + _warpUnderwayCount += 1; + await _pageController.animateToPage(_currentIndex!, + duration: duration, curve: Curves.ease); + _warpUnderwayCount -= 1; + return Future.value(); + } + + assert((_currentIndex! - previousIndex).abs() > 1); + final int initialPage = _currentIndex! > previousIndex + ? _currentIndex! - 1 + : _currentIndex! + 1; + setState(() { + _warpUnderwayCount += 1; + _children = List.of(_children, growable: false); + final Widget temp = _children[initialPage]; + _children[initialPage] = _children[previousIndex]; + _children[previousIndex] = temp; + }); + _pageController.jumpToPage(initialPage); + + await _pageController.animateToPage(_currentIndex!, + duration: duration, curve: Curves.ease); + if (!mounted) return Future.value(); + setState(() { + _warpUnderwayCount -= 1; + }); + } + + // Called when the PageView scrolls + bool _handleScrollNotification(ScrollNotification notification) { + if (_warpUnderwayCount > 0) return false; + + if (notification.depth != 0) return false; + + _warpUnderwayCount += 1; + if (notification is ScrollUpdateNotification && + !_controller.indexIsChanging) { + if ((_pageController.page! - _controller.index).abs() > 1.0) { + _controller.index = _pageController.page!.round(); + _currentIndex = _controller.index; + } + _controller.index = _pageController.page!.round(); + _controller.offset = + (_pageController.page! - _controller.index).clamp(-1.0, 1.0); + } else if (notification is ScrollEndNotification) { + _controller.index = _pageController.page!.round(); + _currentIndex = _controller.index; + if (!_controller.indexIsChanging) { + _controller.offset = + (_pageController.page! - _controller.index).clamp(-1.0, 1.0); + } + } + _warpUnderwayCount -= 1; + + return false; + } + + @override + Widget build(BuildContext context) { + assert(() { + if (_controller.length != widget.router.pageCount) { + throw FlutterError( + "Controller's length property (${_controller.length}) does not match the " + "number of tabs (${widget.router.pageCount}) present in TabsRouter pages count.", + ); + } + return true; + }()); + return NotificationListener( + onNotification: _handleScrollNotification, + child: PageView( + scrollDirection: widget.scrollDirection, + dragStartBehavior: widget.dragStartBehavior, + controller: _pageController, + physics: widget.physics == null + ? const PageScrollPhysics().applyTo(const ClampingScrollPhysics()) + : const PageScrollPhysics().applyTo(widget.physics), + children: _children, + ), + ); + } +} diff --git a/lib/src/router/widgets/stacked_tabs_router.dart b/lib/src/router/widgets/stacked_tabs_router.dart new file mode 100644 index 000000000..43a4635c9 --- /dev/null +++ b/lib/src/router/widgets/stacked_tabs_router.dart @@ -0,0 +1,788 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/src/router/matcher/route_match.dart'; +import 'package:stacked/src/router/widgets/stacked_tab_view.dart'; + +import '../common/common.dart'; +import '../controller/controller_scope.dart'; +import '../controller/routing_controller.dart'; +import '../route/page_route_info.dart'; +import '../stacked_page.dart'; +import 'stacked_page_view.dart'; + +typedef AnimatedIndexedStackBuilder = Widget Function( + BuildContext context, Widget child, Animation animation); +typedef TabsBuilder = Widget Function( + BuildContext context, List children, TabsRouter tabsRouter); +typedef TabsPageViewBuilder = Widget Function( + BuildContext context, Widget child, PageController pageController); +typedef TabsTabBarBuilder = Widget Function( + BuildContext context, Widget child, TabController tabController); +typedef OnNavigationChanged = Function(TabsRouter tabsRouter); + +abstract class StackedTabsRouter extends StatefulWidget { + final List routes; + final NavigatorObserversBuilder navigatorObservers; + final bool inheritNavigatorObservers; + + // if activeIndex != homeIndex + // set activeIndex to homeIndex + // else pop parent + final int homeIndex; + + const StackedTabsRouter._({ + Key? key, + required this.routes, + this.homeIndex = -1, + this.inheritNavigatorObservers = true, + this.navigatorObservers = + NestedRouterDelegate.defaultNavigatorObserversBuilder, + }) : super(key: key); + + const factory StackedTabsRouter({ + Key? key, + required List routes, + bool lazyLoad, + Duration duration, + Curve curve, + AnimatedIndexedStackBuilder? builder, + int homeIndex, + bool inheritNavigatorObservers, + NavigatorObserversBuilder navigatorObservers, + }) = _StackedTabsRouterIndexedStack; + + const factory StackedTabsRouter.pageView({ + Key? key, + required List routes, + TabsPageViewBuilder? builder, + int homeIndex, + bool animatePageTransition, + Axis scrollDirection, + Duration duration, + Curve curve, + bool inheritNavigatorObservers, + NavigatorObserversBuilder navigatorObservers, + ScrollPhysics? physics, + DragStartBehavior dragStartBehavior, + }) = AutoTabsRouterPageView; + + const factory StackedTabsRouter.tabBar({ + Key? key, + required List routes, + TabsTabBarBuilder? builder, + int homeIndex, + Duration? duration, + Axis scrollDirection, + Curve curve, + bool inheritNavigatorObservers, + NavigatorObserversBuilder navigatorObservers, + ScrollPhysics? physics, + DragStartBehavior dragStartBehavior, + }) = _AutoTabsRouterTabBar; + + const factory StackedTabsRouter.builder({ + Key? key, + required List routes, + required TabsBuilder builder, + OnNavigationChanged? onNavigate, + OnNavigationChanged? onRouterReady, + int homeIndex, + bool inheritNavigatorObservers, + NavigatorObserversBuilder navigatorObservers, + }) = _AutoTabsRouterBuilder; + + static TabsRouter of(BuildContext context, {bool watch = false}) { + var scope = TabsRouterScope.of(context, watch: watch); + assert(() { + if (scope == null) { + throw FlutterError( + 'AutoTabsRouter operation requested with a context that does not include an AutoTabsRouter.\n' + 'The context used to retrieve the AutoTabsRouter must be that of a widget that ' + 'is a descendant of an AutoTabsRouter widget.'); + } + return true; + }()); + return scope!.controller; + } +} + +abstract class _StackedTabsRouterState extends State { + TabsRouter? _controller; + late RoutingController _parentController; + + TabsRouter? get controller => _controller; + late List _navigatorObservers; + late NavigatorObserversBuilder _inheritableObserversBuilder; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final parentRoute = RouteData.of(context); + if (_controller == null) { + final parentScope = RouterScope.of(context, watch: true); + _inheritableObserversBuilder = () { + var observers = widget.navigatorObservers(); + if (!widget.inheritNavigatorObservers) { + return observers; + } + var inheritedObservers = parentScope.inheritableObserversBuilder(); + return inheritedObservers + observers; + }; + _navigatorObservers = _inheritableObserversBuilder(); + _parentController = parentScope.controller; + _controller = TabsRouter( + parent: _parentController, + key: parentRoute.key, + homeIndex: widget.homeIndex, + routeData: parentRoute, + routeCollection: _parentController.routeCollection.subCollectionOf( + parentRoute.name, + ), + pageBuilder: _parentController.pageBuilder, + ); + _parentController.attachChildController(_controller!); + _setupController(); + } + } + + void _setupController(); + + @override + void dispose() { + if (_controller != null) { + _controller!.dispose(); + _parentController.removeChildController(_controller!); + _controller = null; + } + super.dispose(); + } +} + +// ----------------------------------------------------------- +class _StackedTabsRouterIndexedStack extends StackedTabsRouter { + final AnimatedIndexedStackBuilder? builder; + final Duration duration; + final Curve curve; + final bool lazyLoad; + + const _StackedTabsRouterIndexedStack({ + Key? key, + required List routes, + this.lazyLoad = true, + this.duration = const Duration(milliseconds: 300), + this.curve = Curves.ease, + this.builder, + int homeIndex = -1, + bool inheritNavigatorObservers = true, + NavigatorObserversBuilder navigatorObservers = + NestedRouterDelegate.defaultNavigatorObserversBuilder, + }) : super._( + key: key, + routes: routes, + inheritNavigatorObservers: inheritNavigatorObservers, + navigatorObservers: navigatorObservers, + homeIndex: homeIndex, + ); + + @override + _AutoTabsRouterIndexedStackState createState() => + _AutoTabsRouterIndexedStackState(); +} + +class _AutoTabsRouterIndexedStackState extends _StackedTabsRouterState + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation _animation; + int _index = 0; + late int _tabsHash; + + _StackedTabsRouterIndexedStack get typedWidget => + widget as _StackedTabsRouterIndexedStack; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + duration: typedWidget.duration, + ); + _animation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + parent: _animationController, + curve: typedWidget.curve, + ), + ); + _tabsHash = const ListEquality().hash(widget.routes); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + void _setupController() { + assert(_controller != null); + _controller!.setupRoutes(widget.routes); + _index = _controller!.activeIndex; + _animationController.value = 1.0; + _controller!.addListener(() { + if (_controller!.activeIndex != _index) { + setState(() { + _index = _controller!.activeIndex; + }); + _animationController.forward(from: 0.0); + } else if (mounted) { + setState(() {}); + } + }); + } + + @override + void didUpdateWidget(covariant StackedTabsRouter oldWidget) { + super.didUpdateWidget(oldWidget); + if (!const ListEquality().equals(widget.routes, oldWidget.routes)) { + _controller!.replaceAll(widget.routes, oldWidget.routes[_index]); + _tabsHash = const ListEquality().hash(widget.routes); + setState(() { + _index = _controller!.activeIndex; + }); + } + } + + @override + Widget build(BuildContext context) { + assert(_controller != null); + final stack = _controller!.stack; + final builder = typedWidget.builder ?? _defaultBuilder; + final stateHash = controller!.stateHash; + + final builderChild = stack.isEmpty + ? Container(color: Theme.of(context).scaffoldBackgroundColor) + : _IndexedStackBuilder( + activeIndex: _index, + tabsHash: _tabsHash, + lazyLoad: typedWidget.lazyLoad, + animation: _animation, + navigatorObservers: _navigatorObservers, + itemBuilder: (BuildContext context, int index) { + return stack[index].buildPage(context); + }, + stack: stack, + ); + + return RouterScope( + controller: _controller!, + inheritableObserversBuilder: _inheritableObserversBuilder, + stateHash: stateHash, + navigatorObservers: _navigatorObservers, + child: TabsRouterScope( + controller: _controller!, + stateHash: stateHash, + child: AnimatedBuilder( + animation: _animation, + builder: (context, child) => builder( + context, + child ?? builderChild, + _animation, + ), + child: builderChild, + ), + ), + ); + } + + Widget _defaultBuilder(_, child, animation) { + return FadeTransition(opacity: animation, child: child); + } +} + +class _IndexedStackBuilder extends StatefulWidget { + const _IndexedStackBuilder({ + Key? key, + required this.activeIndex, + required this.itemBuilder, + required this.navigatorObservers, + required this.stack, + required this.lazyLoad, + required this.tabsHash, + required this.animation, + }) : super(key: key); + + final int activeIndex; + final IndexedWidgetBuilder itemBuilder; + final bool lazyLoad; + final List stack; + final List navigatorObservers; + final int tabsHash; + final Animation animation; + + @override + _IndexedStackBuilderState createState() => _IndexedStackBuilderState(); +} + +class _IndexedStackBuilderState extends State<_IndexedStackBuilder> + with _RouteAwareTabsMixin<_IndexedStackBuilder> { + final _dummyWidget = const SizedBox.shrink(); + final _initializedPagesTracker = {}; + + @override + List get routes => + widget.stack.map((e) => e.routeData.route).toList(); + + @override + List get observers => widget.navigatorObservers; + + @override + void initState() { + super.initState(); + _setup(); + } + + void _setup() { + for (var i = 0; i < widget.stack.length; ++i) { + if (i == widget.activeIndex || !widget.lazyLoad) { + _initializedPagesTracker[i] = true; + _didInitTabRoute(i); + } else { + _initializedPagesTracker[i] = false; + } + } + } + + @override + void didUpdateWidget(_IndexedStackBuilder oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.tabsHash != oldWidget.tabsHash) { + _initializedPagesTracker.clear(); + _setup(); + return; + } + if (widget.lazyLoad && + _initializedPagesTracker[widget.activeIndex] != true) { + _initializedPagesTracker[widget.activeIndex] = true; + _didInitTabRoute(widget.activeIndex, oldWidget.activeIndex); + } else if (widget.activeIndex != oldWidget.activeIndex) { + _didChangeTabRoute(widget.activeIndex, oldWidget.activeIndex); + } + } + + @override + Widget build(BuildContext context) { + return IndexedStack( + key: ValueKey(widget.tabsHash), + index: widget.activeIndex, + sizing: StackFit.expand, + children: List.generate( + widget.stack.length, + (index) { + if (!widget.stack[index].maintainState && + index != widget.activeIndex) { + _initializedPagesTracker[index] = false; + } + final isInitialized = _initializedPagesTracker[index] == true; + return isInitialized + ? widget.itemBuilder(context, index) + : _dummyWidget; + }, + ), + ); + } +} + +class AutoTabsRouterPageView extends StackedTabsRouter { + final TabsPageViewBuilder? _pageViewModeBuilder; + final bool animatePageTransition; + final Duration duration; + final Curve curve; + final Axis scrollDirection; + final ScrollPhysics? physics; + final DragStartBehavior dragStartBehavior; + + const AutoTabsRouterPageView({ + Key? key, + required List routes, + TabsPageViewBuilder? builder, + int homeIndex = -1, + this.scrollDirection = Axis.horizontal, + this.animatePageTransition = true, + this.duration = kTabScrollDuration, + this.curve = Curves.easeInOut, + this.physics, + this.dragStartBehavior = DragStartBehavior.start, + bool inheritNavigatorObservers = true, + NavigatorObserversBuilder navigatorObservers = + NestedRouterDelegate.defaultNavigatorObserversBuilder, + }) : _pageViewModeBuilder = builder, + super._( + key: key, + routes: routes, + homeIndex: homeIndex, + navigatorObservers: navigatorObservers, + inheritNavigatorObservers: inheritNavigatorObservers, + ); + + @override + AutoTabsRouterPageViewState createState() => AutoTabsRouterPageViewState(); +} + +class AutoTabsRouterPageViewState extends _StackedTabsRouterState + with _RouteAwareTabsMixin { + late PageController _pageController; + + @override + void _setupController() { + assert(_controller != null); + _controller!.setupRoutes(widget.routes); + _updatePageController(); + _didInitTabRoute(_controller!.activeIndex); + _controller!.addListener(() { + var controllerPage = 0; + try { + controllerPage = _pageController.page!.toInt(); + } catch (e) { + controllerPage = 0; + } + if (_controller!.activeIndex != controllerPage) { + _didChangeTabRoute( + _controller!.activeIndex, _controller!.previousIndex!); + } + }); + } + + void _updatePageController() { + _pageController = PageController( + initialPage: _controller!.activeIndex, + ); + } + + @override + void didUpdateWidget(covariant AutoTabsRouterPageView oldWidget) { + super.didUpdateWidget(oldWidget); + if (!const ListEquality().equals(widget.routes, oldWidget.routes)) { + _controller!.replaceAll( + widget.routes, oldWidget.routes[_controller!.activeIndex]); + _updatePageController(); + } + } + + AutoTabsRouterPageView get typedWidget => widget as AutoTabsRouterPageView; + + @override + Widget build(BuildContext context) { + assert(_controller != null); + final builder = typedWidget._pageViewModeBuilder ?? _defaultPageViewBuilder; + final stateHash = controller!.stateHash; + return RouterScope( + controller: _controller!, + inheritableObserversBuilder: _inheritableObserversBuilder, + stateHash: stateHash, + navigatorObservers: _navigatorObservers, + child: TabsRouterScope( + controller: _controller!, + stateHash: stateHash, + child: Builder(builder: (context) { + return builder( + context, + StackedPageView( + scrollDirection: typedWidget.scrollDirection, + physics: typedWidget.physics, + dragStartBehavior: typedWidget.dragStartBehavior, + controller: _pageController, + router: _controller!, + ), + _pageController, + ); + }), + ), + ); + } + + Widget _defaultPageViewBuilder(_, Widget child, __) { + return child; + } + + @override + List get observers => _navigatorObservers; + + @override + List get routes => + _controller!.stackData.map((e) => e.route).toList(); +} + +class _AutoTabsRouterTabBar extends StackedTabsRouter { + final TabsTabBarBuilder? builder; + final Duration? duration; + final Curve curve; + final ScrollPhysics? physics; + final DragStartBehavior dragStartBehavior; + final Axis scrollDirection; + const _AutoTabsRouterTabBar({ + Key? key, + required List routes, + this.scrollDirection = Axis.horizontal, + this.builder, + int homeIndex = -1, + this.duration, + this.curve = Curves.ease, + bool inheritNavigatorObservers = true, + NavigatorObserversBuilder navigatorObservers = + NestedRouterDelegate.defaultNavigatorObserversBuilder, + this.physics, + this.dragStartBehavior = DragStartBehavior.start, + }) : super._( + key: key, + routes: routes, + homeIndex: homeIndex, + navigatorObservers: navigatorObservers, + inheritNavigatorObservers: inheritNavigatorObservers, + ); + + @override + _AutoTabsRouterTabBarState createState() => _AutoTabsRouterTabBarState(); +} + +class _AutoTabsRouterTabBarState extends _StackedTabsRouterState + with _RouteAwareTabsMixin, TickerProviderStateMixin { + late TabController _tabController; + + @override + void _setupController() { + assert(_controller != null); + _controller!.setupRoutes(widget.routes); + _updateTabController(); + _didInitTabRoute(_controller!.activeIndex); + _controller!.addListener(() { + if (_controller!.activeIndex != _controller!.previousIndex) { + _didChangeTabRoute( + _controller!.activeIndex, + _controller!.previousIndex ?? 0, + ); + _tabController.animateTo( + _controller!.activeIndex, + duration: typedWidget.duration, + curve: typedWidget.curve, + ); + } + if (mounted) { + setState(() {}); + } + }); + } + + void _updateTabController() { + _tabController = TabController( + initialIndex: _controller!.activeIndex, + length: _controller!.pageCount, + vsync: this, + ); + _tabController.addListener(() { + _controller!.setActiveIndex(_tabController.index); + }); + } + + @override + void didUpdateWidget(covariant _AutoTabsRouterTabBar oldWidget) { + super.didUpdateWidget(oldWidget); + if (!const ListEquality().equals(widget.routes, oldWidget.routes)) { + _controller! + .replaceAll(widget.routes, oldWidget.routes[_tabController.index]); + _updateTabController(); + } + } + + _AutoTabsRouterTabBar get typedWidget => widget as _AutoTabsRouterTabBar; + + @override + Widget build(BuildContext context) { + assert(_controller != null); + final builder = typedWidget.builder ?? _defaultPageViewBuilder; + final stateHash = controller!.stateHash; + return RouterScope( + controller: _controller!, + inheritableObserversBuilder: _inheritableObserversBuilder, + stateHash: stateHash, + navigatorObservers: _navigatorObservers, + child: TabsRouterScope( + controller: _controller!, + stateHash: stateHash, + child: Builder(builder: (context) { + return builder( + context, + StackedTabView( + scrollDirection: typedWidget.scrollDirection, + physics: typedWidget.physics, + dragStartBehavior: typedWidget.dragStartBehavior, + controller: _tabController, + router: _controller!, + ), + _tabController, + ); + }), + ), + ); + } + + Widget _defaultPageViewBuilder(_, Widget child, __) { + return child; + } + + @override + List get observers => _navigatorObservers; + + @override + List get routes => + _controller!.stackData.map((e) => e.route).toList(); +} + +class _AutoTabsRouterBuilder extends StackedTabsRouter { + final TabsBuilder builder; + final OnNavigationChanged? onNavigate; + final OnNavigationChanged? onRouterReady; + + const _AutoTabsRouterBuilder({ + Key? key, + required List routes, + this.onNavigate, + this.onRouterReady, + required this.builder, + int homeIndex = -1, + bool inheritNavigatorObservers = true, + NavigatorObserversBuilder navigatorObservers = + NestedRouterDelegate.defaultNavigatorObserversBuilder, + }) : super._( + key: key, + routes: routes, + homeIndex: homeIndex, + navigatorObservers: navigatorObservers, + inheritNavigatorObservers: inheritNavigatorObservers, + ); + + @override + _AutoTabsRouterBuilderState createState() => _AutoTabsRouterBuilderState(); +} + +class _AutoTabsRouterBuilderState extends _StackedTabsRouterState + with _RouteAwareTabsMixin { + @override + void _setupController() { + assert(_controller != null); + _controller!.setupRoutes(widget.routes); + typedWidget.onRouterReady?.call(_controller!); + _didInitTabRoute(_controller!.activeIndex); + _controller!.addListener(() { + if (_controller!.activeIndex != _controller!.previousIndex) { + _didChangeTabRoute( + _controller!.activeIndex, _controller!.previousIndex ?? 0); + typedWidget.onNavigate?.call(_controller!); + } + if (mounted) { + setState(() {}); + } + }); + } + + @override + void didUpdateWidget(covariant _AutoTabsRouterBuilder oldWidget) { + super.didUpdateWidget(oldWidget); + if (!const ListEquality().equals(widget.routes, oldWidget.routes)) { + _controller!.replaceAll( + widget.routes, oldWidget.routes[_controller!.activeIndex]); + typedWidget.onNavigate?.call(_controller!); + } + } + + _AutoTabsRouterBuilder get typedWidget => widget as _AutoTabsRouterBuilder; + + @override + Widget build(BuildContext context) { + assert(_controller != null); + final stack = _controller!.stack; + final builder = typedWidget.builder; + final stateHash = controller!.stateHash; + return RouterScope( + controller: _controller!, + inheritableObserversBuilder: _inheritableObserversBuilder, + stateHash: stateHash, + navigatorObservers: _navigatorObservers, + child: TabsRouterScope( + controller: _controller!, + stateHash: stateHash, + child: Builder(builder: (context) { + return builder( + context, + List.generate( + stack.length, + (index) => KeepAliveTab( + key: ValueKey(index), + page: stack[index], + ), + ), + _controller!, + ); + }), + ), + ); + } + + @override + List get observers => _navigatorObservers; + + @override + List get routes => + _controller!.stackData.map((e) => e.route).toList(); +} + +mixin _RouteAwareTabsMixin on State { + List get observers; + + List get routes; + + void _didInitTabRoute(int index, [int previous = -1]) { + observers.whereType().forEach((observer) { + TabPageRoute? previousRoute; + if (previous != -1) { + previousRoute = + TabPageRoute(routeInfo: routes[previous], index: previous); + } + observer.didInitTabRoute( + TabPageRoute(routeInfo: routes[index], index: index), + previousRoute, + ); + }); + } + + void _didChangeTabRoute(int index, int previous) { + observers.whereType().forEach((observer) { + observer.didChangeTabRoute( + TabPageRoute(routeInfo: routes[index], index: index), + TabPageRoute(routeInfo: routes[previous], index: previous), + ); + }); + } +} + +class KeepAliveTab extends StatefulWidget { + const KeepAliveTab({ + Key? key, + required this.page, + }) : super(key: key); + final StackedPage page; + + @override + State createState() => _KeepAliveTabState(); +} + +class _KeepAliveTabState extends State + with AutomaticKeepAliveClientMixin { + @override + Widget build(BuildContext context) { + super.build(context); + return widget.page.buildPage(context); + } + + @override + bool get wantKeepAlive => widget.page.maintainState; +} diff --git a/lib/src/router/widgets/wrapped_route.dart b/lib/src/router/widgets/wrapped_route.dart new file mode 100644 index 000000000..56f7f7858 --- /dev/null +++ b/lib/src/router/widgets/wrapped_route.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/src/router/common/route_wrapper.dart'; + +@optionalTypeArgs +class WrappedRoute extends StatelessWidget { + const WrappedRoute({Key? key, required this.child}) : super(key: key); + final T child; + + @override + Widget build(BuildContext context) { + return child.wrappedRoute(context); + } +} diff --git a/lib/src/utils.dart b/lib/src/utils.dart new file mode 100644 index 000000000..fa9229cd1 --- /dev/null +++ b/lib/src/utils.dart @@ -0,0 +1,7 @@ +bool mapNullOrEmpty(Map? map) { + return (map == null || map.isEmpty); +} + +bool listNullOrEmpty(Iterable? iterable) { + return (iterable == null || iterable.isEmpty); +} diff --git a/lib/src/view_models/base_view_models.dart b/lib/src/view_models/base_view_models.dart new file mode 100644 index 000000000..06fdb0d9f --- /dev/null +++ b/lib/src/view_models/base_view_models.dart @@ -0,0 +1,171 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:stacked/src/mixins/listenable_service_mixin.dart'; +import 'package:stacked/src/mixins/reactive_service_mixin.dart'; +import 'package:stacked/src/view_models/helpers/data_state_helper.dart'; + +import 'helpers/builders_helpers.dart'; +import 'helpers/busy_error_state_helper.dart'; +import 'helpers/message_state_helper.dart'; + +/// Contains ViewModel functionality for busy and error state management +class BaseViewModel extends ChangeNotifier + with BuilderHelpers, BusyAndErrorStateHelper { + @override + void notifyListeners() { + if (!disposed) { + super.notifyListeners(); + } + } + + /// Calls the builder function with this updated viewmodel + void rebuildUi() { + notifyListeners(); + } + + @override + void dispose() { + disposed = true; + super.dispose(); + } +} + +/// A [BaseViewModel] that provides functionality to subscribe to a reactive service. +abstract class ReactiveViewModel extends BaseViewModel { + List _listenableServices = []; + List get listenableServices => []; + + // ignore: deprecated_member_use_from_same_package + List _reactiveServices = []; + + @Deprecated('Use listenableServices property instead') + List get reactiveServices => []; + + ReactiveViewModel() { + if (listenableServices.isNotEmpty) _reactToServices(listenableServices); + + // ignore: deprecated_member_use_from_same_package + if (reactiveServices.isNotEmpty) { + // ignore: deprecated_member_use_from_same_package + _reactToServicesDeprecated(reactiveServices); + } + } + + void _reactToServices(List listenableServices) { + _listenableServices = listenableServices; + for (var listenableService in _listenableServices) { + listenableService.addListener(_indicateChange); + } + } + + // ignore: deprecated_member_use_from_same_package + void _reactToServicesDeprecated(List reactiveServices) { + _reactiveServices = reactiveServices; + for (var reactiveService in _reactiveServices) { + reactiveService.addListener(_indicateChange); + } + } + + @override + void dispose() { + for (var listenableService in _listenableServices) { + listenableService.removeListener(_indicateChange); + } + + for (var reactiveService in _reactiveServices) { + reactiveService.removeListener(_indicateChange); + } + super.dispose(); + } + + void _indicateChange() { + notifyListeners(); + } +} + +@protected +class DynamicSourceViewModel extends ReactiveViewModel { + bool changeSource = false; + void notifySourceChanged() { + changeSource = true; + } + + @override + List get listenableServices => []; +} + +/// Interface: Additional actions that should be implemented by spcialised ViewModels +abstract class Initialisable { + void initialise(); +} + +class StreamData extends DynamicSourceViewModel + with MessageStateHelper, DataStateHelper { + Stream stream; + + /// Called when the new data arrives + /// + /// notifyListeners is called before this so no need to call in here unless you're + /// running additional logic and setting a separate value. + Function? onData; + + /// Called after the stream has been listened too + Function? onSubscribed; + + /// Called when an error is placed on the stream + Function? onError; + + /// Called when the stream is cancelled + Function? onCancel; + + /// Allows you to modify the data before it's set as the new data for the ViewModel + /// + /// This can be used to modify the data if required. If nothhing is returned the data + /// won't be set. + Function? transformData; + StreamData( + this.stream, { + this.onData, + this.onSubscribed, + this.onError, + this.onCancel, + this.transformData, + }); + late StreamSubscription _streamSubscription; + + void initialise() { + _streamSubscription = stream.listen( + (incomingData) { + setError(null); + setMessage(null); + + // Extra security in case transformData isnt sent + if (transformData == null) { + data = incomingData; + } else { + data = transformData!(incomingData) ?? incomingData; + } + + notifyListeners(); + onData!(data); + }, + onError: (error) { + setError(error); + data = null; + onError!(error); + notifyListeners(); + }, + ); + + onSubscribed!(); + } + + @override + void dispose() { + _streamSubscription.cancel(); + onCancel!(); + + super.dispose(); + } +} diff --git a/lib/src/view_models/data_models/multiple_data_models.dart b/lib/src/view_models/data_models/multiple_data_models.dart new file mode 100644 index 000000000..9dc759ec9 --- /dev/null +++ b/lib/src/view_models/data_models/multiple_data_models.dart @@ -0,0 +1,180 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; + +import '../base_view_models.dart'; + +class _MultiDataSourceViewModel + extends DynamicSourceViewModel { + Map? _dataMap; + Map? get dataMap => _dataMap; + + bool dataReady(K key) => _dataMap![key] != null && (error(key) == null); +} + +/// Provides functionality for a ViewModel to run and fetch data using multiple future +abstract class MultipleFutureViewModel extends _MultiDataSourceViewModel + implements Initialisable { + Map get futuresMap; + + late Completer _futuresCompleter; + late int _futuresCompleted; + + void _initialiseData() { + _dataMap ??= {}; + + _futuresCompleted = 0; + } + + @override + Future initialise() { + _futuresCompleter = Completer(); + _initialiseData(); + // We set busy manually as well because when notify listeners is called to clear error messages the + // ui is rebuilt and if you expect busy to be true it's not. + setBusy(true); + notifyListeners(); + + for (var key in futuresMap.keys) { + runBusyFuture(futuresMap[key]!(), busyObject: key, throwException: true) + .then((futureData) { + _dataMap![key] = futureData; + setBusyForObject(key, false); + notifyListeners(); + onData(key); + _incrementAndCheckFuturesCompleted(); + }).catchError((error) { + setErrorForObject(key, error); + setBusyForObject(key, false); + onError(key: key, error: error); + notifyListeners(); + _incrementAndCheckFuturesCompleted(); + }); + } + setBusy(false); + changeSource = false; + + return _futuresCompleter.future; + } + + void _incrementAndCheckFuturesCompleted() { + _futuresCompleted++; + if (_futuresCompleted == futuresMap.length && + !_futuresCompleter.isCompleted) { + _futuresCompleter.complete(); + onAllFuturesCompleted(); + } + } + + void onError({String? key, error}) {} + + void onData(String key) {} + + void onAllFuturesCompleted() {} +} + +/// Provides functionality for a ViewModel to run and fetch data using multiple streams +abstract class MultipleStreamViewModel extends _MultiDataSourceViewModel + implements Initialisable { + // Every MultipleStreamViewModel must override streamDataMap + // StreamData requires a stream, but lifecycle events are optional + // if a lifecyle event isn't defined we use the default ones here + Map get streamsMap; + + Map? _streamsSubscriptions; + + @visibleForTesting + Map? get streamsSubscriptions => + _streamsSubscriptions; + + /// Returns the stream subscription associated with the key + StreamSubscription? getSubscriptionForKey(String key) => + _streamsSubscriptions![key]; + + @override + void initialise() { + _dataMap = {}; + clearErrors(); + _streamsSubscriptions = {}; + + if (!changeSource) { + notifyListeners(); + } + final streamsMapValues = Map.from(streamsMap); + + for (final key in streamsMapValues.keys) { + // If a lifecycle function isn't supplied, we fallback to default + _streamsSubscriptions![key] = streamsMapValues[key]!.stream.listen( + (incomingData) { + setErrorForObject(key, null); + notifyListeners(); + // Extra security in case transformData isnt sent + if (streamsMapValues[key]!.transformData == null) { + _dataMap![key] = transformData(key, incomingData) ?? incomingData; + } else { + _dataMap![key] = + streamsMapValues[key]!.transformData!(incomingData) ?? + incomingData; + } + + notifyListeners(); + streamsMapValues[key]!.onData != null + ? streamsMapValues[key]!.onData!(_dataMap![key]) + : onData(key, _dataMap![key]); + }, + onError: (error) { + setErrorForObject(key, error); + _dataMap![key] = null; + + streamsMapValues[key]!.onError != null + ? streamsMapValues[key]!.onError!(error) + : onError(key, error); + notifyListeners(); + }, + ); + streamsMapValues[key]!.onSubscribed != null + ? streamsMapValues[key]!.onSubscribed!() + : onSubscribed(key); + changeSource = false; + } + } + + @override + void notifySourceChanged({bool clearOldData = false}) { + changeSource = true; + _disposeAllSubscriptions(); + + if (clearOldData) { + dataMap!.clear(); + clearErrors(); + } + + notifyListeners(); + } + + void onData(String key, dynamic data) {} + void onSubscribed(String key) {} + void onError(String key, error) {} + void onCancel(String key) {} + dynamic transformData(String key, data) { + return data; + } + + @override + @mustCallSuper + void dispose() { + _disposeAllSubscriptions(); + super.dispose(); + } + + void _disposeAllSubscriptions() { + if (_streamsSubscriptions != null) { + for (var key in _streamsSubscriptions!.keys) { + _streamsSubscriptions![key]!.cancel(); + onCancel(key); + } + + _streamsSubscriptions!.clear(); + } + } +} diff --git a/lib/src/view_models/data_models/single_data_models.dart b/lib/src/view_models/data_models/single_data_models.dart new file mode 100644 index 000000000..6568f1d06 --- /dev/null +++ b/lib/src/view_models/data_models/single_data_models.dart @@ -0,0 +1,134 @@ +import 'dart:async'; + +import 'package:stacked/src/view_models/helpers/data_state_helper.dart'; + +import '../base_view_models.dart'; +import '../helpers/message_state_helper.dart'; + +/// Provides functionality for a ViewModel that's sole purpose it is to fetch data using a [Future] +/// This class is mixed with mixins: +/// - [MessageStateHelper] +/// - [DataStateHelper] +abstract class FutureViewModel extends DynamicSourceViewModel + with MessageStateHelper, DataStateHelper + implements Initialisable { + // TODO: Add timeout functionality + // TODO: Add retry functionality - default 1 + // TODO: Add retry lifecycle hooks to override in the viewmodel + + /// The future that fetches the data and sets the view to busy + Future futureToRun(); + + /// Indicates if you want the error caught in futureToRun to be rethrown + bool get rethrowException => false; + + @override + Future initialise() async { + setError(null); + setMessage(null); + setBusy(true); + + try { + data = await runBusyFuture(futureToRun(), throwException: true); + } catch (exception, stackTrace) { + setError(exception); + setBusy(false); + onError(exception, stackTrace); + + notifyListeners(); + if (rethrowException) { + rethrow; + } + + return null; + } + + if (data != null) { + onData(data); + } + + changeSource = false; + } + + /// Called when an error occurs within the future being run + void onError(dynamic error, StackTrace? stackTrace) {} + + /// Called after the data has been set + void onData(T? data) {} +} + +/// Provides functionality for a ViewModel that's sole purpose it is to fetch data using a [Stream] +/// This class is mixed with mixins: +/// - [MessageStateHelper] +/// - [DataStateHelper] +abstract class StreamViewModel extends DynamicSourceViewModel + with MessageStateHelper, DataStateHelper + implements Initialisable { + /// Stream to listen to + Stream get stream; + + StreamSubscription? get streamSubscription => _streamSubscription; + + StreamSubscription? _streamSubscription; + + @override + void notifySourceChanged({bool clearOldData = false}) { + changeSource = true; + _streamSubscription?.cancel(); + _streamSubscription = null; + + if (clearOldData) { + data = null; + } + + notifyListeners(); + } + + @override + void initialise() { + _streamSubscription = stream.listen( + (incomingData) { + setError(null); + setMessage(null); + // Extra security in case transformData isnt sent + data = transformData(incomingData) ?? incomingData; + + onData(data); + notifyListeners(); + }, + onError: (dynamic error, StackTrace? stackTrace) { + setError(error); + data = null; + onError(error, stackTrace); + notifyListeners(); + }, + ); + + onSubscribed(); + changeSource = false; + } + + /// Called before the notifyListeners is called when data has been set + void onData(T? data) {} + + /// Called when the stream is listened too + void onSubscribed() {} + + /// Called when an error is fired in the stream + void onError(dynamic error, StackTrace? stackTrace) {} + + void onCancel() {} + + /// Called before the data is set for the ViewModel + T transformData(T data) { + return data; + } + + @override + void dispose() { + _streamSubscription!.cancel(); + onCancel(); + + super.dispose(); + } +} diff --git a/lib/src/view_models/form_view_model.dart b/lib/src/view_models/form_view_model.dart new file mode 100644 index 000000000..bf42d7c3a --- /dev/null +++ b/lib/src/view_models/form_view_model.dart @@ -0,0 +1,8 @@ +import 'package:stacked/stacked.dart'; + +/// You can use [BaseViewModel] or [ReactiveViewModel] with a [FormStateHelper] +/// to achive the same result incase you want to combine multiple functionalities +abstract class FormViewModel extends ReactiveViewModel with FormStateHelper { + @override + List get listenableServices => []; +} diff --git a/lib/src/view_models/helpers/builders_helpers.dart b/lib/src/view_models/helpers/builders_helpers.dart new file mode 100644 index 000000000..669a9f7a1 --- /dev/null +++ b/lib/src/view_models/helpers/builders_helpers.dart @@ -0,0 +1,21 @@ +/// Essential helper to work with the [ViewModelBuilder] +mixin BuilderHelpers { + bool disposed = false; + + bool _initialised = false; + bool get initialised => _initialised; + + bool _onModelReadyCalled = false; + bool get onModelReadyCalled => _onModelReadyCalled; + + /// Sets the initialised value for the ViewModel to true. This is called after + /// the first initialise special ViewModel call + void setInitialised(bool value) { + _initialised = value; + } + + /// Sets the onModelReadyCalled value to true. This is called after this first onModelReady call + void setOnModelReadyCalled(bool value) { + _onModelReadyCalled = value; + } +} diff --git a/lib/src/view_models/helpers/busy_error_state_helper.dart b/lib/src/view_models/helpers/busy_error_state_helper.dart new file mode 100644 index 000000000..a77267d03 --- /dev/null +++ b/lib/src/view_models/helpers/busy_error_state_helper.dart @@ -0,0 +1,120 @@ +import 'package:flutter/foundation.dart'; + +mixin BusyAndErrorStateHelper on ChangeNotifier { + final Map _busyStates = {}; + + /// Returns the busy status for an object if it exists. Returns false if not present + bool busy(Object? object) => _busyStates[object.hashCode] ?? false; + + /// Returns the busy status of the ViewModel + bool get isBusy => busy(this); + + // Returns true if any objects still have a busy status that is true. + bool get anyObjectsBusy => _busyStates.values.any((busy) => busy); + + /// Sets the busy state for the object equal to the value passed in and notifies Listeners + /// If you're using a primitive type the value SHOULD NOT BE CHANGED, since Hashcode uses == value + void setBusyForObject(Object? object, bool value) { + _busyStates[object.hashCode] = value; + notifyListeners(); + } + + /// Marks the ViewModel as busy and calls notify listeners + void setBusy(bool value) { + setBusyForObject(this, value); + } + + /// returns real data passed if neither the model is busy nor the object passed is busy + T skeletonData( + {required T? realData, required T busyData, Object? busyKey}) { + /// If busyKey is supplied we check busy(busyKey) to see if that property is busy + /// If it is we return busyData, else realData + bool isBusyKeySupplied = busyKey != null; + if ((isBusyKeySupplied && busy(busyKey)) || realData == null) { + return busyData; + } else if (!isBusyKeySupplied && isBusy) { + return busyData; + } + + return realData; + } + + /// Sets the ViewModel to busy, runs the future and then sets it to not busy when complete. + /// + /// rethrows [Exception] after setting busy to false for object or class + Future runBusyFuture(Future busyFuture, + {Object? busyObject, bool throwException = false}) async { + _setBusyForModelOrObject(true, busyObject: busyObject); + try { + var value = await runErrorFuture(busyFuture, + key: busyObject, throwException: throwException); + return value; + } catch (e) { + if (throwException) rethrow; + return Future.value(); + } finally { + _setBusyForModelOrObject(false, busyObject: busyObject); + } + } + + void _setBusyForModelOrObject(bool value, {Object? busyObject}) { + if (busyObject != null) { + setBusyForObject(busyObject, value); + } else { + setBusyForObject(this, value); + } + } + + final Map _errorStates = {}; + dynamic error(Object object) => _errorStates[object.hashCode]; + + /// Returns the error existence status of the ViewModel + bool get hasError => error(this) != null; + + /// Returns the error status of the ViewModel + dynamic get modelError => error(this); + + /// Clears all the errors + void clearErrors() { + _errorStates.clear(); + } + + /// Returns a boolean that indicates if the ViewModel has an error for the key + bool hasErrorForKey(Object key) => error(key) != null; + + /// Sets the error for the ViewModel + void setError(dynamic error) { + setErrorForObject(this, error); + } + + void setErrorForModelOrObject(dynamic value, {Object? key}) { + if (key != null) { + setErrorForObject(key, value); + } else { + setErrorForObject(this, value); + } + } + + /// Sets the error state for the object equal to the value passed in and notifies Listeners + /// If you're using a primitive type the value SHOULD NOT BE CHANGED, since Hashcode uses == value + void setErrorForObject(Object object, dynamic value) { + _errorStates[object.hashCode] = value; + notifyListeners(); + } + + Future runErrorFuture(Future future, + {Object? key, bool throwException = false}) async { + try { + setErrorForModelOrObject(null, key: key); + return await future; + } catch (e) { + setErrorForModelOrObject(e, key: key); + onFutureError(e, key); + if (throwException) rethrow; + return Future.value(); + } + } + + /// Function that is called when a future throws an error + void onFutureError(dynamic error, Object? key) {} +} diff --git a/lib/src/view_models/helpers/data_state_helper.dart b/lib/src/view_models/helpers/data_state_helper.dart new file mode 100644 index 000000000..29ed82c13 --- /dev/null +++ b/lib/src/view_models/helpers/data_state_helper.dart @@ -0,0 +1,15 @@ +import 'package:stacked/src/view_models/helpers/busy_error_state_helper.dart'; + +/// Helper class to store a data object +mixin DataStateHelper on BusyAndErrorStateHelper { + T? _data; + + T? get data => _data; + + set data(T? data) { + _data = data; + } + + /// Data is ready to be consumed + bool get dataReady => _data != null && !hasError && !isBusy; +} diff --git a/packages/stacked/lib/src/state_management/form_viewmodel.dart b/lib/src/view_models/helpers/form_state_helper.dart similarity index 68% rename from packages/stacked/lib/src/state_management/form_viewmodel.dart rename to lib/src/view_models/helpers/form_state_helper.dart index 110dd1c18..5c28ae436 100644 --- a/packages/stacked/lib/src/state_management/form_viewmodel.dart +++ b/lib/src/view_models/helpers/form_state_helper.dart @@ -1,12 +1,8 @@ -import 'package:stacked/src/state_management/base_view_models.dart'; -import 'package:stacked/src/state_management/reactive_service_mixin.dart'; +import 'package:flutter/material.dart'; /// Provides functionality to reduce the code required in order to move user input /// into the [ViewModel] -abstract class FormViewModel extends ReactiveViewModel { - @override - List get reactiveServices => []; - +mixin FormStateHelper on ChangeNotifier { bool _showValidationMessage = false; bool get showValidationMessage => _showValidationMessage; @@ -14,8 +10,8 @@ abstract class FormViewModel extends ReactiveViewModel { String? get validationMessage => _validationMessage; /// Stores the mapping of the form key to the value entered by the user - Map formValueMap = Map(); - Map fieldsValidationMessages = Map(); + Map formValueMap = {}; + Map fieldsValidationMessages = {}; void setValidationMessage(String? value) { _validationMessage = value; @@ -41,7 +37,14 @@ abstract class FormViewModel extends ReactiveViewModel { notifyListeners(); } + void setValidationMessages(Map validationMessages) { + fieldsValidationMessages = validationMessages; + fieldsValidationMessages.removeWhere((key, value) => value == null); + setFormStatus(); + notifyListeners(); + } + /// Called after the [formValueMap] has been updated and allows you to set /// values relating to the forms status. - void setFormStatus(); + void setFormStatus() {} } diff --git a/lib/src/view_models/helpers/index_tracking_state_helper.dart b/lib/src/view_models/helpers/index_tracking_state_helper.dart new file mode 100644 index 000000000..7c4c3ab8b --- /dev/null +++ b/lib/src/view_models/helpers/index_tracking_state_helper.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/src/router/router_service_interface.dart'; + +mixin IndexTrackingStateHelper on ChangeNotifier { + int _currentIndex = 0; + int get currentIndex => _currentIndex; + + bool _reverse = false; + + /// Indicates whether we're going forward or backward in terms of the index we're changing. + /// This is very helpful for the page transition directions. + bool get reverse => _reverse; + + void setIndex(int value) { + if (value < _currentIndex) { + _reverse = true; + } else { + _reverse = false; + } + _currentIndex = value; + notifyListeners(); + } + + bool isIndexSelected(int index) => _currentIndex == index; + + /// Sets current index using current Route on Web Platform. + /// + /// Allows to get the index from url during a browser refresh which means the + /// app starts from scracth. + void setCurrentWebPageIndex(RouterServiceInterface service) { + if (!service.topRoute.hasPendingChildren) return; + + int? currentWebPageIndex; + + try { + currentWebPageIndex = service.router.routes + .firstWhere( + (route) => route.name == service.topRoute.name, + ) + .children + ?.routes + .skipWhile((route) => route.name.startsWith('#')) + .toList() + .indexWhere( + (route) => + route.name == service.topRoute.pendingChildren.first.name, + ); + } on StateError catch (_) { + /// The route does not meet the condition in the firstWhere call + } catch (_) { + /// Catch everything else + } + + if (currentWebPageIndex == null || currentWebPageIndex == -1) return; + + setIndex(currentWebPageIndex); + } +} diff --git a/lib/src/view_models/helpers/message_state_helper.dart b/lib/src/view_models/helpers/message_state_helper.dart new file mode 100644 index 000000000..4ba607680 --- /dev/null +++ b/lib/src/view_models/helpers/message_state_helper.dart @@ -0,0 +1,33 @@ +import 'package:flutter/foundation.dart'; + +mixin MessageStateHelper on ChangeNotifier { + final Map _messageStates = {}; + + /// Returns the message for an object if it exists. Returns null if not present + String? message(Object object) => _messageStates[object.hashCode]; + + /// Returns the message status of the ViewModel + bool get hasMessage => message(this) != null; + + /// Returns the message status of the ViewModel + String? get modelMessage => message(this); + + /// Returns a boolean that indicates if the ViewModel has an message for the key + bool hasMessageForKey(Object key) => message(key) != null; + + void clearMessages() { + _messageStates.clear(); + } + + /// Sets the message for the ViewModel + void setMessage(String? message) { + setMessageForObject(this, message); + } + + /// Sets the message for the object equal to the value passed in and notifies Listeners + /// If you're using a primitive type the value SHOULD NOT BE CHANGED, since Hashcode uses == value + void setMessageForObject(Object object, String? value) { + _messageStates[object.hashCode] = value; + notifyListeners(); + } +} diff --git a/lib/src/view_models/index_tracking_viewmodel.dart b/lib/src/view_models/index_tracking_viewmodel.dart new file mode 100644 index 000000000..cc3295f9f --- /dev/null +++ b/lib/src/view_models/index_tracking_viewmodel.dart @@ -0,0 +1,12 @@ +// import 'package:stacked/src/mixins/listenable_service_mixin.dart'; +// import 'package:stacked/src/router/router_service_interface.dart'; +// import 'package:stacked/src/view_models/base_view_models.dart'; +import 'package:stacked/stacked.dart'; + +/// You can use [BaseViewModel] or [ReactiveViewModel] with a [IndexTrackingStateHelper] +/// to achive the same result incase you want to combine multiple functionalities +abstract class IndexTrackingViewModel extends ReactiveViewModel + with IndexTrackingStateHelper { + @override + List get listenableServices => []; +} diff --git a/lib/src/view_models/selector_view_model_builder.dart b/lib/src/view_models/selector_view_model_builder.dart new file mode 100644 index 000000000..f22e331d7 --- /dev/null +++ b/lib/src/view_models/selector_view_model_builder.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class SelectorViewModelBuilder + extends StatelessWidget { + const SelectorViewModelBuilder({ + Key? key, + required this.selector, + required this.builder, + this.child, + this.shouldRebuild, + }) : super(key: key); + + final K Function(T viewModel) selector; + final Widget Function(BuildContext context, K value, Widget? child) builder; + final Widget? child; + final bool Function(K, K)? shouldRebuild; + @override + Widget build(BuildContext context) { + return Selector( + key: key, + shouldRebuild: shouldRebuild, + selector: (BuildContext context, T viewModel) => selector(viewModel), + builder: (BuildContext _, K value, Widget? child) => + builder(_, value, child), + child: child, + ); + } +} diff --git a/lib/src/view_models/selector_view_model_builder_widget.dart b/lib/src/view_models/selector_view_model_builder_widget.dart new file mode 100644 index 000000000..77c484390 --- /dev/null +++ b/lib/src/view_models/selector_view_model_builder_widget.dart @@ -0,0 +1,46 @@ +import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; + +abstract class SelectorViewModelWidget + extends Widget { + const SelectorViewModelWidget({Key? key}) : super(key: key); + + K selector(T viewModel); + Widget? get staticChild => null; + bool shouldRebuild(K v1, K v2) => v1 != v2; + Widget build(BuildContext context, K value); + + @override + // ignore: library_private_types_in_public_api + _DataProviderElement createElement() => + _DataProviderElement(this); +} + +class _DataProviderElement + extends ComponentElement { + _DataProviderElement(SelectorViewModelWidget widget) : super(widget); + + @override + SelectorViewModelWidget get widget => + super.widget as SelectorViewModelWidget; + + @override + Widget build() { + return Selector( + key: widget.key, + shouldRebuild: widget.shouldRebuild, + selector: (BuildContext context, T viewModel) => + widget.selector(viewModel), + builder: (BuildContext _, K value, Widget? child) => + widget.build(this, value), + child: widget.staticChild, + ); + } + + @override + void update(SelectorViewModelWidget newWidget) { + super.update(newWidget); + assert(widget == newWidget); + rebuild(); + } +} diff --git a/packages/stacked/lib/src/state_management/view_model_builder_widget.dart b/lib/src/view_models/stacked_view.dart similarity index 58% rename from packages/stacked/lib/src/state_management/view_model_builder_widget.dart rename to lib/src/view_models/stacked_view.dart index 7c69428ae..4dc7182d8 100644 --- a/packages/stacked/lib/src/state_management/view_model_builder_widget.dart +++ b/lib/src/view_models/stacked_view.dart @@ -1,21 +1,22 @@ import 'package:flutter/widgets.dart'; -import 'package:stacked/src/state_management/view_model_builder.dart'; +import 'package:stacked/src/view_models/view_model_builder.dart'; -/// A widget that wraps the [ViewModelBuilder] class in a less boiler plate use of the widget -/// -/// Default [reactive] value is true. Can be overriden and set to false +@Deprecated( + 'This widget will be removed by March 2023, please use the StackedView instead') abstract class ViewModelBuilderWidget - extends StatelessWidget { + extends StackedView { const ViewModelBuilderWidget({Key? key}) : super(key: key); +} + +/// Creates a binding between the ViewModel and the UI by providing a builder function that +/// gets called when the viewmodel changes has occured. +abstract class StackedView extends StatelessWidget { + const StackedView({Key? key}) : super(key: key); /// A function that builds the UI to be shown from the ViewModel - Required /// /// [viewModel] is the ViewModel passed in and [child] is the [staticChildBuilder] result - Widget builder( - BuildContext context, - T viewModel, - Widget? child, - ); + Widget builder(BuildContext context, T viewModel, Widget? child); /// A builder that builds the ViewModel for this UI - Required T viewModelBuilder(BuildContext context); @@ -29,10 +30,10 @@ abstract class ViewModelBuilderWidget /// When set to true a new ViewModel will be constructed everytime the widget is inserted. /// /// When setting this to true make sure to handle all disposing of streams if subscribed - /// to any in the ViewModel. [onModelReady] will fire once the ViewModel has been created/set. + /// to any in the ViewModel. [onViewModelReady] will fire once the ViewModel has been created/set. /// This will be used when on re-insert of the widget the ViewModel has to be constructed with /// a new value. - bool get createNewModelOnInsert => false; + bool get createNewViewModelOnInsert => false; /// Indicates if you want Provider to dispose the ViewModel when it's removed from the widget tree. /// @@ -43,15 +44,26 @@ abstract class ViewModelBuilderWidget /// every time it's inserted into the widget tree. bool get initialiseSpecialViewModelsOnce => false; - /// Indicates if you want to fire onModelReady only once or everytime this widget is inserted into + /// Indicates if you want to fire onViewModelReady only once or everytime this widget is inserted into /// the widget tree. - bool get fireOnModelReadyOnce => false; + bool get fireOnViewModelReadyOnce => false; /// Fires when the ViewModel is first created or re-created /// - /// This will fire multiple times when [createNewModelOnInsert] is set to true + /// This will fire multiple times when [createNewViewModelOnInsert] is set to true void onViewModelReady(T viewModel) {} + /// Fires when the ViewModel is first created or re-created + /// + /// This will fire multiple times when [createNewViewModelOnInsert] is set to true + @Deprecated('Prefer to use onViewModelReady istead') + void onModelReady(T viewModel) {} + + /// Fires when the ViewModel is disposed + /// + /// Useful when working with a form on the view to dispose the form. + void onDispose(T viewModel) {} + /// A Function that builds UI for the static child that builds only once /// /// When [reactive] is set to false the builder is used as the static child @@ -65,21 +77,23 @@ abstract class ViewModelBuilderWidget builder: builder, viewModelBuilder: () => viewModelBuilder(context), staticChild: staticChildBuilder(context), - onModelReady: onViewModelReady, + onViewModelReady: onViewModelReady, + onDispose: onDispose, disposeViewModel: disposeViewModel, - createNewModelOnInsert: createNewModelOnInsert, + createNewViewModelOnInsert: createNewViewModelOnInsert, initialiseSpecialViewModelsOnce: initialiseSpecialViewModelsOnce, - fireOnModelReadyOnce: fireOnModelReadyOnce, + fireOnViewModelReadyOnce: fireOnViewModelReadyOnce, ); } else { return ViewModelBuilder.nonReactive( builder: builder, viewModelBuilder: () => viewModelBuilder(context), - onModelReady: onViewModelReady, + onViewModelReady: onViewModelReady, + onDispose: onDispose, disposeViewModel: disposeViewModel, - createNewModelOnInsert: createNewModelOnInsert, + createNewViewModelOnInsert: createNewViewModelOnInsert, initialiseSpecialViewModelsOnce: initialiseSpecialViewModelsOnce, - fireOnModelReadyOnce: fireOnModelReadyOnce, + fireOnViewModelReadyOnce: fireOnViewModelReadyOnce, ); } } diff --git a/packages/stacked/lib/src/state_management/skeleton_loader.dart b/lib/src/view_models/ui/skeleton_loader.dart similarity index 85% rename from packages/stacked/lib/src/state_management/skeleton_loader.dart rename to lib/src/view_models/ui/skeleton_loader.dart index b1718ba57..a88510196 100644 --- a/packages/stacked/lib/src/state_management/skeleton_loader.dart +++ b/lib/src/view_models/ui/skeleton_loader.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:flutter/painting.dart'; const Color _veryLightGrey = Color(0xFFE3E3E3); const Color _lightGrey = Color(0xFF848B9E); -const int _TransitionDuration = 500; +const int _transitionDuration = 500; /// A widget that allows you to provide the expected UI and will render a shimmer over that /// while loading is true. @@ -14,6 +13,8 @@ class SkeletonLoader extends StatefulWidget { final Color startColor; final Color endColor; final Duration duration; + final double cornerRadius; + const SkeletonLoader({ Key? key, required this.child, @@ -21,13 +22,14 @@ class SkeletonLoader extends StatefulWidget { this.duration = const Duration(seconds: 1), this.startColor = _veryLightGrey, this.endColor = _lightGrey, + this.cornerRadius = 15.0, }) : super(key: key); @override - _SkeletonLoaderState createState() => _SkeletonLoaderState(); + SkeletonLoaderState createState() => SkeletonLoaderState(); } -class _SkeletonLoaderState extends State +class SkeletonLoaderState extends State with TickerProviderStateMixin { late AnimationController _controller; late Animation animationOne; @@ -46,9 +48,7 @@ class _SkeletonLoaderState extends State ); // Store the loading widget we first constructed with - if (_initialWidget == null) { - _initialWidget = widget.child; - } + _initialWidget ??= widget.child; animationOne = ColorTween(begin: widget.startColor, end: widget.endColor) .animate(_controller); @@ -83,7 +83,8 @@ class _SkeletonLoaderState extends State // has now updated to match the actual data. // We will use a delayed future to only fade out the shader mask // a few milliseconds after we have received the actual widget. - Future.delayed(Duration(milliseconds: _TransitionDuration)).then((value) { + Future.delayed(const Duration(milliseconds: _transitionDuration)) + .then((value) { if (!_dispose) { setState(() { // print('&^&^&^&^& - Set widet transition to false!!'); @@ -101,13 +102,9 @@ class _SkeletonLoaderState extends State // We only want to show this if the widget is loading OR if the widget is busy with the transition. child: widget.loading || _transitionToNewWidget ? AnimatedSize( - duration: Duration(milliseconds: 450), + duration: const Duration(milliseconds: 450), curve: Curves.easeOut, child: ShaderMask( - child: CustomPaint( - child: widget.child, - foregroundPainter: RectangleFillPainter(), - ), blendMode: BlendMode.srcATop, shaderCallback: (rect) { return LinearGradient(colors: [ @@ -115,6 +112,11 @@ class _SkeletonLoaderState extends State animationTwo.value!, ]).createShader(rect); }, + child: CustomPaint( + foregroundPainter: RectangleFillPainter( + cornerRadius: widget.cornerRadius), + child: widget.child, + ), ), ) : widget.child), @@ -131,12 +133,16 @@ class _SkeletonLoaderState extends State class RectangleFillPainter extends CustomPainter { bool hasPainted = true; + final double cornerRadius; + + RectangleFillPainter({required this.cornerRadius}); + @override void paint(Canvas canvas, Size size) { canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromLTWH(0, 0, size.width, size.height), - Radius.circular(15.0), + Radius.circular(cornerRadius), ), Paint()..color = Colors.grey); } diff --git a/packages/stacked/lib/src/state_management/view_model_builder.dart b/lib/src/view_models/view_model_builder.dart similarity index 67% rename from packages/stacked/lib/src/state_management/view_model_builder.dart rename to lib/src/view_models/view_model_builder.dart index b5f1d02be..044ebcb6f 100644 --- a/packages/stacked/lib/src/state_management/view_model_builder.dart +++ b/lib/src/view_models/view_model_builder.dart @@ -1,8 +1,9 @@ import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; + import 'base_view_models.dart'; -enum _ViewModelBuilderType { NonReactive, Reactive } +enum ViewModelBuilderType { nonReactive, reactive } /// A widget that provides base functionality for the Mvvm style provider architecture by FilledStacks. class ViewModelBuilder extends StatefulWidget { @@ -10,11 +11,18 @@ class ViewModelBuilder extends StatefulWidget { /// Fires once when the ViewModel is created or set for the first time /// - /// If you want this to fire everytime the widget is inserted set [createNewModelOnInsert] to true - final Function(T model)? onModelReady; + /// If you want this to fire everytime the widget is inserted set [createNewViewModelOnInsert] to true + final Function(T viewModel)? onViewModelReady; + + @Deprecated('Prefer to use onViewModelReady instead') + final Function(T viewModel)? onModelReady; /// Builder function with access to the ViewModel to build UI form - final Widget Function(BuildContext context, T model, Widget? child) builder; + final Widget Function( + BuildContext context, + T viewModel, + Widget? child, + ) builder; /// A builder function that returns the ViewModel for this widget final T Function() viewModelBuilder; @@ -27,15 +35,18 @@ class ViewModelBuilder extends StatefulWidget { /// When set to true a new ViewModel will be constructed everytime the widget is inserted. /// /// When setting this to true make sure to handle all disposing of streams if subscribed - /// to any in the ViewModel. [onModelReady] will fire once the ViewModel has been created/set. + /// to any in the ViewModel. [onViewModelReady] will fire once the ViewModel has been created/set. /// This will be used when on re-insert of the widget the ViewModel has to be constructed with /// a new value. - final bool createNewModelOnInsert; + final bool createNewViewModelOnInsert; - final _ViewModelBuilderType providerType; + final ViewModelBuilderType providerType; - /// Indicates if the onModelReady should fire every time the ViewModel is inserted into the widget tree. + /// Indicates if the onViewModelReady should fire every time the ViewModel is inserted into the widget tree. /// Or only once during the lifecycle of the ViewModel. + final bool fireOnViewModelReadyOnce; + + @Deprecated('Prefer to use fireOnViewModelReadyOnce') final bool fireOnModelReadyOnce; /// Indicates if we should run the initialise functionality for special ViewModels only once @@ -43,7 +54,7 @@ class ViewModelBuilder extends StatefulWidget { /// Fires when the widget has been removed from the widget tree and allows you to dispose /// of any controllers or state values that need disposing - final Function(T model)? onDispose; + final Function(T viewModel)? onDispose; /// Constructs a ViewModel provider that will not rebuild the provided widget when notifyListeners is called. /// @@ -51,14 +62,17 @@ class ViewModelBuilder extends StatefulWidget { const ViewModelBuilder.nonReactive({ required this.viewModelBuilder, required this.builder, - this.onModelReady, + @Deprecated('Prefer to use onViewModelReady instead') this.onModelReady, + this.onViewModelReady, this.onDispose, this.disposeViewModel = true, - this.createNewModelOnInsert = false, + this.createNewViewModelOnInsert = false, + this.fireOnViewModelReadyOnce = false, + @Deprecated('Prefer to use fireOnViewModelReadyOnce') this.fireOnModelReadyOnce = false, this.initialiseSpecialViewModelsOnce = false, Key? key, - }) : providerType = _ViewModelBuilderType.NonReactive, + }) : providerType = ViewModelBuilderType.nonReactive, staticChild = null, super(key: key); @@ -66,22 +80,25 @@ class ViewModelBuilder extends StatefulWidget { const ViewModelBuilder.reactive({ required this.viewModelBuilder, required this.builder, + @Deprecated('Prefer to use onViewModelReady instead') this.onModelReady, this.staticChild, - this.onModelReady, + this.onViewModelReady, this.onDispose, this.disposeViewModel = true, - this.createNewModelOnInsert = false, + this.createNewViewModelOnInsert = false, + this.fireOnViewModelReadyOnce = false, + @Deprecated('Prefer to use fireOnViewModelReadyOnce') this.fireOnModelReadyOnce = false, this.initialiseSpecialViewModelsOnce = false, Key? key, - }) : providerType = _ViewModelBuilderType.Reactive, + }) : providerType = ViewModelBuilderType.reactive, super(key: key); @override - _ViewModelBuilderState createState() => _ViewModelBuilderState(); + ViewModelBuilderState createState() => ViewModelBuilderState(); } -class _ViewModelBuilderState +class ViewModelBuilderState extends State> { T? _viewModel; @@ -93,7 +110,7 @@ class _ViewModelBuilderState _createViewModel(); } // Or if the user wants to create a new ViewModel whenever initState is fired - else if (widget.createNewModelOnInsert) { + else if (widget.createNewViewModelOnInsert) { _createViewModel(); } } @@ -109,13 +126,30 @@ class _ViewModelBuilderState _initialiseSpecialViewModels(); } + // Fire onViewModelReady after the ViewModel has been constructed + if (widget.onViewModelReady != null) { + if (widget.fireOnViewModelReadyOnce && + !(_viewModel as BaseViewModel).onModelReadyCalled) { + widget.onViewModelReady!(_viewModel!); + (_viewModel as BaseViewModel?)?.setOnModelReadyCalled(true); + } else if (!widget.fireOnViewModelReadyOnce) { + widget.onViewModelReady!(_viewModel!); + } + } + + // TODO: Delete this code on +5 minor version increases // Fire onModelReady after the ViewModel has been constructed + // ignore: deprecated_member_use_from_same_package if (widget.onModelReady != null) { + // ignore: deprecated_member_use_from_same_package if (widget.fireOnModelReadyOnce && !(_viewModel as BaseViewModel).onModelReadyCalled) { + // ignore: deprecated_member_use_from_same_package widget.onModelReady!(_viewModel!); (_viewModel as BaseViewModel?)?.setOnModelReadyCalled(true); + // ignore: deprecated_member_use_from_same_package } else if (!widget.fireOnModelReadyOnce) { + // ignore: deprecated_member_use_from_same_package widget.onModelReady!(_viewModel!); } } @@ -135,7 +169,7 @@ class _ViewModelBuilderState @override Widget build(BuildContext context) { - if (widget.providerType == _ViewModelBuilderType.NonReactive) { + if (widget.providerType == ViewModelBuilderType.nonReactive) { if (!widget.disposeViewModel) { return ChangeNotifierProvider.value( value: _viewModel!, @@ -181,4 +215,5 @@ class _ViewModelBuilderState } /// EXPERIMENTAL: Returns the ViewModel provided above this widget in the tree -T getParentViewModel(BuildContext context) => Provider.of(context); +T getParentViewModel(BuildContext context, {bool listen = true}) => + Provider.of(context, listen: listen); diff --git a/packages/stacked/lib/src/state_management/view_model_widget.dart b/lib/src/view_models/view_model_widget.dart similarity index 74% rename from packages/stacked/lib/src/state_management/view_model_widget.dart rename to lib/src/view_models/view_model_widget.dart index 8435bc9a7..7f99ef5e3 100644 --- a/packages/stacked/lib/src/state_management/view_model_widget.dart +++ b/lib/src/view_models/view_model_widget.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; @@ -12,11 +11,11 @@ abstract class ViewModelWidget extends Widget { Widget build(BuildContext context, T viewModel); @override - _DataProviderElement createElement() => _DataProviderElement(this); + DataProviderElement createElement() => DataProviderElement(this); } -class _DataProviderElement extends ComponentElement { - _DataProviderElement(ViewModelWidget widget) : super(widget); +class DataProviderElement extends ComponentElement { + DataProviderElement(ViewModelWidget widget) : super(widget); @override ViewModelWidget get widget => super.widget as ViewModelWidget; @@ -29,6 +28,6 @@ class _DataProviderElement extends ComponentElement { void update(ViewModelWidget newWidget) { super.update(newWidget); assert(widget == newWidget); - rebuild(); + rebuild(force: true); } } diff --git a/lib/stacked.dart b/lib/stacked.dart new file mode 100644 index 000000000..48b11e8f9 --- /dev/null +++ b/lib/stacked.dart @@ -0,0 +1,60 @@ +library stacked; + +/// code generators +export 'src/code_generation/router_annotation/extended_navigator.dart'; +export 'src/code_generation/router_annotation/route_def.dart'; +export 'src/code_generation/router_annotation/router_base.dart'; +export 'src/code_generation/router_annotation/router_utils.dart'; +export 'src/code_generation/router_annotation/transitions_builders.dart'; + +/// mixins +export 'src/mixins/listenable_service_mixin.dart'; +export 'src/mixins/reactive_service_mixin.dart'; +export 'src/reactive/reactive_list/reactive_list.dart'; +export 'src/reactive/reactive_value/reactive_value.dart'; +export 'src/router/auto_router_x.dart'; +export 'src/router/controller/controller_scope.dart'; +export 'src/router/controller/routing_controller.dart'; + +/// Navigator 2.0 Exports +export 'src/router/matcher/route_match.dart'; +export 'src/router/navigation_failure.dart'; +export 'src/router/router_service_interface.dart'; +export 'src/router/parser/route_information_parser.dart'; +export 'src/router/route/page_route_info.dart'; +export 'src/router/route/route_config.dart'; +export 'src/router/route/route_data_scope.dart'; +export 'src/router/stacked_page.dart'; +export 'src/router/widgets/auto_tabs_scaffold.dart'; +export 'src/router/widgets/custom_cupertino_transitions_builder.dart' + show NoShadowCupertinoPageTransitionsBuilder; +export 'src/router/widgets/deferred_widget.dart'; +export 'src/router/widgets/nested_router.dart'; +export 'src/router/widgets/route_navigator.dart'; +export 'src/router/widgets/stacked_leading_button.dart'; +export 'src/router/widgets/stacked_page_view.dart'; +export 'src/router/widgets/stacked_tabs_router.dart'; +export 'src/router/widgets/wrapped_route.dart'; + +/// viewmodels +export 'src/view_models/base_view_models.dart'; +export 'src/view_models/data_models/multiple_data_models.dart'; +export 'src/view_models/data_models/single_data_models.dart'; + +/// deprecated viewmodels +export 'src/view_models/form_view_model.dart'; +export 'src/view_models/helpers/data_state_helper.dart'; +export 'src/view_models/helpers/form_state_helper.dart'; +export 'src/view_models/helpers/index_tracking_state_helper.dart'; + +/// viewmodel helpers +export 'src/view_models/helpers/message_state_helper.dart'; +export 'src/view_models/index_tracking_viewmodel.dart'; +export 'src/view_models/selector_view_model_builder.dart'; +export 'src/view_models/selector_view_model_builder_widget.dart'; +export 'src/view_models/stacked_view.dart'; + +/// ui +export 'src/view_models/ui/skeleton_loader.dart'; +export 'src/view_models/view_model_builder.dart'; +export 'src/view_models/view_model_widget.dart'; diff --git a/lib/stacked_annotations.dart b/lib/stacked_annotations.dart new file mode 100644 index 000000000..e1e9cbb4f --- /dev/null +++ b/lib/stacked_annotations.dart @@ -0,0 +1 @@ +export 'package:stacked_shared/stacked_shared.dart'; diff --git a/packages/stacked/.gitignore b/packages/stacked/.gitignore deleted file mode 100644 index a5408d960..000000000 --- a/packages/stacked/.gitignore +++ /dev/null @@ -1,76 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Flutter.podspec -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/Flutter/flutter_export_environment.sh -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages -*.lock diff --git a/packages/stacked/example/.gitignore b/packages/stacked/example/.gitignore deleted file mode 100644 index 6ff4a095a..000000000 --- a/packages/stacked/example/.gitignore +++ /dev/null @@ -1,39 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Web related -lib/generated_plugin_registrant.dart - -# Exceptions to above rules. -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages - -thirdparty/ diff --git a/packages/stacked/example/.metadata b/packages/stacked/example/.metadata deleted file mode 100644 index 4adf4bf02..000000000 --- a/packages/stacked/example/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 - channel: stable - -project_type: app diff --git a/packages/stacked/example/.vscode/launch.json b/packages/stacked/example/.vscode/launch.json deleted file mode 100644 index 181350794..000000000 --- a/packages/stacked/example/.vscode/launch.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Flutter", - "request": "launch", - "type": "dart" - } - ] -} \ No newline at end of file diff --git a/packages/stacked/example/android/app/src/main/AndroidManifest.xml b/packages/stacked/example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 8bbe8e78c..000000000 --- a/packages/stacked/example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - diff --git a/packages/stacked/example/android/app/src/main/kotlin/com/example/new_architecture/MainActivity.kt b/packages/stacked/example/android/app/src/main/kotlin/com/example/new_architecture/MainActivity.kt deleted file mode 100644 index 7e0aad794..000000000 --- a/packages/stacked/example/android/app/src/main/kotlin/com/example/new_architecture/MainActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.new_architecture - -import androidx.annotation.NonNull; -import io.flutter.embedding.android.FlutterActivity -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugins.GeneratedPluginRegistrant - -class MainActivity: FlutterActivity() { - override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { - GeneratedPluginRegistrant.registerWith(flutterEngine); - } -} diff --git a/packages/stacked/example/lib/app/app.dart b/packages/stacked/example/lib/app/app.dart deleted file mode 100644 index 4f875aee2..000000000 --- a/packages/stacked/example/lib/app/app.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:new_architecture/services/epoch_service.dart'; -import 'package:new_architecture/services/factory_service.dart'; -import 'package:new_architecture/services/information_service.dart'; -import 'package:new_architecture/ui/bottom_nav/bottom_nav_example.dart'; -import 'package:new_architecture/ui/bottom_nav/favorites/favorites_viewmodel.dart'; -import 'package:new_architecture/ui/bottom_nav/history/history_viewmodel.dart'; -import 'package:new_architecture/ui/details/details_view.dart'; -import 'package:new_architecture/ui/form/example_form_view.dart'; -import 'package:new_architecture/ui/home/home_view.dart'; -import 'package:new_architecture/ui/nonreactive/nonreactive_view.dart'; -import 'package:new_architecture/ui/stream_view/stream_counter_view.dart'; -import 'package:stacked/stacked.dart'; -import 'package:stacked/stacked_annotations.dart'; -import 'package:stacked_services/stacked_services.dart'; -import 'package:stacked_themes/stacked_themes.dart'; - -@StackedApp( - routes: [ - MaterialRoute(page: HomeView, initial: true), - MaterialRoute(page: BottomNavExample), - MaterialRoute(page: StreamCounterView), - CupertinoRoute>>(page: DetailsView), - // TODO: Change the name of the FormView to avoid type clashing - MaterialRoute(page: ExampleFormView), - CustomRoute( - page: NonReactiveView, - transitionsBuilder: TransitionsBuilders.slideBottom, - ), - ], - dependencies: [ - // Lazy singletons - LazySingleton(classType: DialogService), - LazySingleton(classType: BottomSheetService), - // LazySingleton( - // classType: InformationService, - // dispose: disposeInformationService, - // ), - LazySingleton( - classType: NavigationService, environments: {Environment.dev}), - LazySingleton(classType: EpochService), - LazySingleton( - classType: ThemeService, - resolveUsing: ThemeService.getInstance, - ), - LazySingleton(classType: InformationService), - FactoryWithParam(classType: FactoryService), - // singletons - Singleton(classType: HistoryViewModel), - Singleton(classType: FavoritesViewModel), - ], - logger: StackedLogger(), - locatorName: 'exampleLocator', - locatorSetupName: 'setupExampleLocator', -) -class App { - /** This class has no puporse besides housing the annotation that generates the required functionality **/ -} diff --git a/packages/stacked/example/lib/app/app.router.dart b/packages/stacked/example/lib/app/app.router.dart deleted file mode 100644 index 58480634f..000000000 --- a/packages/stacked/example/lib/app/app.router.dart +++ /dev/null @@ -1,115 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ************************************************************************** -// StackedRouterGenerator -// ************************************************************************** - -// ignore_for_file: public_member_api_docs - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; -import 'package:stacked/stacked_annotations.dart'; - -import '../ui/bottom_nav/bottom_nav_example.dart'; -import '../ui/details/details_view.dart'; -import '../ui/form/example_form_view.dart'; -import '../ui/home/home_view.dart'; -import '../ui/nonreactive/nonreactive_view.dart'; -import '../ui/stream_view/stream_counter_view.dart'; - -class Routes { - static const String homeView = '/'; - static const String bottomNavExample = '/bottom-nav-example'; - static const String streamCounterView = '/stream-counter-view'; - static const String detailsView = '/details-view'; - static const String exampleFormView = '/example-form-view'; - static const String nonReactiveView = '/non-reactive-view'; - static const all = { - homeView, - bottomNavExample, - streamCounterView, - detailsView, - exampleFormView, - nonReactiveView, - }; -} - -class StackedRouter extends RouterBase { - @override - List get routes => _routes; - final _routes = [ - RouteDef(Routes.homeView, page: HomeView), - RouteDef(Routes.bottomNavExample, page: BottomNavExample), - RouteDef(Routes.streamCounterView, page: StreamCounterView), - RouteDef(Routes.detailsView, page: DetailsView), - RouteDef(Routes.exampleFormView, page: ExampleFormView), - RouteDef(Routes.nonReactiveView, page: NonReactiveView), - ]; - @override - Map get pagesMap => _pagesMap; - final _pagesMap = { - HomeView: (data) { - return MaterialPageRoute( - builder: (context) => const HomeView(), - settings: data, - ); - }, - BottomNavExample: (data) { - return MaterialPageRoute( - builder: (context) => const BottomNavExample(), - settings: data, - ); - }, - StreamCounterView: (data) { - return MaterialPageRoute( - builder: (context) => StreamCounterView(), - settings: data, - ); - }, - DetailsView: (data) { - var args = data.getArgs(nullOk: false); - return CupertinoPageRoute>>( - builder: (context) => DetailsView( - key: args.key, - name: args.name, - ), - settings: data, - ); - }, - ExampleFormView: (data) { - var args = data.getArgs( - orElse: () => ExampleFormViewArguments(), - ); - return MaterialPageRoute( - builder: (context) => ExampleFormView(key: args.key), - settings: data, - ); - }, - NonReactiveView: (data) { - return PageRouteBuilder( - pageBuilder: (context, animation, secondaryAnimation) => - const NonReactiveView(), - settings: data, - transitionsBuilder: TransitionsBuilders.slideBottom, - ); - }, - }; -} - -/// ************************************************************************ -/// Arguments holder classes -/// ************************************************************************* - -/// DetailsView arguments holder class -class DetailsViewArguments { - final Key? key; - final String name; - DetailsViewArguments({this.key, required this.name}); -} - -/// ExampleFormView arguments holder class -class ExampleFormViewArguments { - final Key? key; - ExampleFormViewArguments({this.key}); -} diff --git a/packages/stacked/example/lib/services/information_service.dart b/packages/stacked/example/lib/services/information_service.dart deleted file mode 100644 index d94a916f6..000000000 --- a/packages/stacked/example/lib/services/information_service.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:stacked/stacked.dart'; - -class InformationService with ReactiveServiceMixin { - final _postCount = ReactiveValue(0); - int get postCount => _postCount.value; - - void updatePostCount() => _postCount.value++; - - void resetCount() => _postCount.value = 0; - - InformationService() { - listenToReactiveValues([_postCount]); - } -} diff --git a/packages/stacked/example/lib/ui/bottom_nav/bottom_nav_example.dart b/packages/stacked/example/lib/ui/bottom_nav/bottom_nav_example.dart deleted file mode 100644 index a30aad2a9..000000000 --- a/packages/stacked/example/lib/ui/bottom_nav/bottom_nav_example.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:new_architecture/ui/bottom_nav/profile/profile_view.dart'; -import 'package:stacked/stacked.dart'; - -import 'bottom_nav_example_viewmodel.dart'; -import 'favorites/favorites_view.dart'; -import 'history/history_view.dart'; - -class BottomNavExample extends StatefulWidget { - const BottomNavExample({Key? key}) : super(key: key); - - @override - _BottomNavExampleState createState() => _BottomNavExampleState(); -} - -class _BottomNavExampleState extends State { - final Map _viewCache = Map(); - - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - builder: (context, viewModel, child) => Scaffold( - body: getViewForIndex(viewModel.currentTabIndex), - bottomNavigationBar: BottomNavigationBar( - elevation: 6, - backgroundColor: Colors.white, - currentIndex: viewModel.currentTabIndex, - onTap: viewModel.setTabIndex, - items: [ - BottomNavigationBarItem( - title: SizedBox(), - icon: Icon(Icons.ac_unit), - ), - BottomNavigationBarItem( - title: SizedBox(), - icon: Icon(Icons.access_alarm), - ), - BottomNavigationBarItem( - title: SizedBox(), - icon: Icon(Icons.access_alarms), - ), - ], - ), - ), - viewModelBuilder: () => BottomNavExampleViewModel(), - ); - } - - Widget getViewForIndex(int index) { - if (!_viewCache.containsKey(index)) { - switch (index) { - case 0: - _viewCache[index] = FavoritesView(); - break; - case 1: - _viewCache[index] = HistoryView(); - break; - case 2: - _viewCache[index] = ProfileView(); - break; - } - } - - return _viewCache[index]!; - } -} diff --git a/packages/stacked/example/lib/ui/bottom_nav/bottom_nav_example_viewmodel.dart b/packages/stacked/example/lib/ui/bottom_nav/bottom_nav_example_viewmodel.dart deleted file mode 100644 index 1fd3732c5..000000000 --- a/packages/stacked/example/lib/ui/bottom_nav/bottom_nav_example_viewmodel.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:stacked/stacked.dart'; - -class BottomNavExampleViewModel extends BaseViewModel { - int _currentTabIndex = 0; - int get currentTabIndex => _currentTabIndex; - - bool _reverse = false; - bool get reverse => _reverse; - - void setTabIndex(int value) { - if (value < _currentTabIndex) { - _reverse = true; - } - _currentTabIndex = value; - notifyListeners(); - } -} diff --git a/packages/stacked/example/lib/ui/details/details_view.dart b/packages/stacked/example/lib/ui/details/details_view.dart deleted file mode 100644 index 1c33436d6..000000000 --- a/packages/stacked/example/lib/ui/details/details_view.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:new_architecture/app/app.router.dart'; -import 'package:stacked_services/stacked_services.dart'; - -class DetailsView extends StatelessWidget { - final String name; - - const DetailsView({Key? key, required this.name}) : super(key: key); - - @override - Widget build(BuildContext context) { - NavigationService _navigationService = NavigationService(); - _navigationService.navigateTo( - Routes.detailsView, - arguments: DetailsViewArguments(name: 'FilledStacks'), - ); - return Container( - child: Text(name), - ); - } -} diff --git a/packages/stacked/example/lib/ui/dumb_widgets/duplicate_name_widget.dart b/packages/stacked/example/lib/ui/dumb_widgets/duplicate_name_widget.dart deleted file mode 100644 index eb7062925..000000000 --- a/packages/stacked/example/lib/ui/dumb_widgets/duplicate_name_widget.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:new_architecture/datamodels/human.dart'; -import 'package:stacked/stacked.dart'; - -class DuplicateNameWidget extends ViewModelWidget { - @override - Widget build(BuildContext context, Human model) { - return Row( - children: [ - Container( - child: Text(model.name!), - ), - SizedBox( - width: 50, - ), - Container( - child: Text(model.name!), - ), - ], - ); - } -} diff --git a/packages/stacked/example/lib/ui/dumb_widgets/full_name_widget.dart b/packages/stacked/example/lib/ui/dumb_widgets/full_name_widget.dart deleted file mode 100644 index a6bfe35ed..000000000 --- a/packages/stacked/example/lib/ui/dumb_widgets/full_name_widget.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:new_architecture/datamodels/human.dart'; -import 'package:stacked/stacked.dart'; - -class FullNameWidget extends ViewModelWidget { - @override - Widget build(BuildContext context, Human model) { - return Row( - children: [ - Container( - child: Text( - model.name!, - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), - ), - ), - SizedBox( - width: 50, - ), - Container( - child: Text( - model.surname!, - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30), - ), - ), - ], - ); - } -} diff --git a/packages/stacked/example/lib/ui/form/example_form_view.dart b/packages/stacked/example/lib/ui/form/example_form_view.dart deleted file mode 100644 index 9dbd90db8..000000000 --- a/packages/stacked/example/lib/ui/form/example_form_view.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:stacked/stacked.dart'; -import 'package:stacked/stacked_annotations.dart'; - -import 'example_form_view.form.dart'; -import 'example_form_viewmodel.dart'; - -// #1: Add the annotation -@FormView(fields: [ - FormTextField(name: 'email', initialValue: "Lorem"), - FormTextField(name: 'password', isPassword: true), - FormTextField(name: 'shortBio'), - FormDateField(name: 'birthDate'), - FormDropdownField( - name: 'doYouLoveFood', - items: [ - StaticDropdownItem( - title: 'Yes', - value: 'YesDr', - ), - StaticDropdownItem( - title: 'No', - value: 'NoDr', - ), - ], - ) -]) -// #2: with $ExampleFormView -class ExampleFormView extends StatelessWidget with $ExampleFormView { - ExampleFormView({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - onModelReady: (viewModel) { - // #3: Listen to text updates by calling listenToFormUpdated(model); - listenToFormUpdated(viewModel); - viewModel.setDoYouLoveFood(DoYouLoveFoodValueToTitleMap.keys.first); - }, - builder: (context, viewModel, child) => Scaffold( - floatingActionButton: FloatingActionButton( - onPressed: () { - viewModel.navigateSomewhere(); - }, - ), - body: SizedBox( - width: MediaQuery.of(context).size.width, - child: Form( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: 300, - ), - child: TextFormField( - //#4: Set email emailController and focus node - controller: emailController, - decoration: InputDecoration(hintText: 'email'), - keyboardType: TextInputType.emailAddress, - focusNode: emailFocusNode, - ), - ), - SizedBox(height: 15), - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: 300, - ), - child: TextFormField( - //#5: Set password passwordController and focus node - controller: passwordController, - decoration: InputDecoration(hintText: 'password'), - keyboardType: TextInputType.visiblePassword, - obscureText: true, - focusNode: passwordFocusNode, - onFieldSubmitted: (_) => viewModel.saveData(), - ), - ), - if (viewModel.hasPasswordValidationMessage) - Text( - viewModel.passwordValidationMessage!, - style: TextStyle(color: Colors.red), - ), - SizedBox(height: 15), - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: 300, - ), - child: TextField( - //#6: Set shortBio shortBioController and focus node - maxLines: null, - keyboardType: TextInputType.multiline, - controller: shortBioController, - decoration: InputDecoration( - hintText: 'Tell us a bit more about yourself', - ), - focusNode: shortBioFocusNode, - ), - ), - SizedBox(height: 15), - ElevatedButton( - onPressed: () => viewModel.selectBirthDate( - context: context, - firstDate: DateTime(1950), - initialDate: DateTime.now(), - lastDate: DateTime(2023)), - child: Text( - viewModel.hasBirthDate - ? viewModel.birthDateValue.toString() - : 'Select your Date of birth', - ), - ), - SizedBox(height: 15), - Row( - children: [ - Text('Do you love food?'), - SizedBox(width: 15), - DropdownButton( - value: viewModel.doYouLoveFoodValue, - onChanged: (value) { - viewModel.setDoYouLoveFood(value!); - }, - items: DoYouLoveFoodValueToTitleMap.keys - .map( - (value) => DropdownMenuItem( - value: value, - child: Text(DoYouLoveFoodValueToTitleMap[value]!), - ), - ) - .toList(), - ) - ], - ), - SizedBox(height: 15), - if (viewModel.showFormValidationMessage) - Text( - viewModel.formValidationMessage!, - style: TextStyle(color: Colors.red), - ), - ], - ), - ), - ), - ), - viewModelBuilder: () => ExampleFormViewModel(), - ); - } -} diff --git a/packages/stacked/example/lib/ui/form/example_form_view.form.dart b/packages/stacked/example/lib/ui/form/example_form_view.form.dart deleted file mode 100644 index 1f039c092..000000000 --- a/packages/stacked/example/lib/ui/form/example_form_view.form.dart +++ /dev/null @@ -1,132 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ************************************************************************** -// StackedFormGenerator -// ************************************************************************** - -// ignore_for_file: public_member_api_docs - -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; - -const String EmailValueKey = 'email'; -const String PasswordValueKey = 'password'; -const String ShortBioValueKey = 'shortBio'; -const String BirthDateValueKey = 'birthDate'; -const String DoYouLoveFoodValueKey = 'doYouLoveFood'; - -const Map DoYouLoveFoodValueToTitleMap = { - 'YesDr': 'Yes', - 'NoDr': 'No', -}; - -mixin $ExampleFormView on StatelessWidget { - final TextEditingController emailController = - TextEditingController(text: 'Lorem'); - final TextEditingController passwordController = TextEditingController(); - final TextEditingController shortBioController = TextEditingController(); - final FocusNode emailFocusNode = FocusNode(); - final FocusNode passwordFocusNode = FocusNode(); - final FocusNode shortBioFocusNode = FocusNode(); - - /// Registers a listener on every generated controller that calls [model.setData()] - /// with the latest textController values - void listenToFormUpdated(FormViewModel model) { - emailController.addListener(() => _updateFormData(model)); - passwordController.addListener(() => _updateFormData(model)); - shortBioController.addListener(() => _updateFormData(model)); - } - - /// Updates the formData on the FormViewModel - void _updateFormData(FormViewModel model) => model.setData( - model.formValueMap - ..addAll({ - EmailValueKey: emailController.text, - PasswordValueKey: passwordController.text, - ShortBioValueKey: shortBioController.text, - }), - ); - - /// Calls dispose on all the generated controllers and focus nodes - void disposeForm() { - // The dispose function for a TextEditingController sets all listeners to null - - emailController.dispose(); - emailFocusNode.dispose(); - passwordController.dispose(); - passwordFocusNode.dispose(); - shortBioController.dispose(); - shortBioFocusNode.dispose(); - } -} - -extension ValueProperties on FormViewModel { - String? get emailValue => this.formValueMap[EmailValueKey]; - String? get passwordValue => this.formValueMap[PasswordValueKey]; - String? get shortBioValue => this.formValueMap[ShortBioValueKey]; - DateTime? get birthDateValue => this.formValueMap[BirthDateValueKey]; - String? get doYouLoveFoodValue => this.formValueMap[DoYouLoveFoodValueKey]; - - bool get hasEmail => this.formValueMap.containsKey(EmailValueKey); - bool get hasPassword => this.formValueMap.containsKey(PasswordValueKey); - bool get hasShortBio => this.formValueMap.containsKey(ShortBioValueKey); - bool get hasBirthDate => this.formValueMap.containsKey(BirthDateValueKey); - bool get hasDoYouLoveFood => - this.formValueMap.containsKey(DoYouLoveFoodValueKey); - - bool get hasEmailValidationMessage => - this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false; - bool get hasPasswordValidationMessage => - this.fieldsValidationMessages[PasswordValueKey]?.isNotEmpty ?? false; - bool get hasShortBioValidationMessage => - this.fieldsValidationMessages[ShortBioValueKey]?.isNotEmpty ?? false; - bool get hasBirthDateValidationMessage => - this.fieldsValidationMessages[BirthDateValueKey]?.isNotEmpty ?? false; - bool get hasDoYouLoveFoodValidationMessage => - this.fieldsValidationMessages[DoYouLoveFoodValueKey]?.isNotEmpty ?? false; - - String? get emailValidationMessage => - this.fieldsValidationMessages[EmailValueKey]; - String? get passwordValidationMessage => - this.fieldsValidationMessages[PasswordValueKey]; - String? get shortBioValidationMessage => - this.fieldsValidationMessages[ShortBioValueKey]; - String? get birthDateValidationMessage => - this.fieldsValidationMessages[BirthDateValueKey]; - String? get doYouLoveFoodValidationMessage => - this.fieldsValidationMessages[DoYouLoveFoodValueKey]; -} - -extension Methods on FormViewModel { - Future selectBirthDate( - {required BuildContext context, - required DateTime initialDate, - required DateTime firstDate, - required DateTime lastDate}) async { - final selectedDate = await showDatePicker( - context: context, - initialDate: initialDate, - firstDate: firstDate, - lastDate: lastDate); - if (selectedDate != null) { - this.setData( - this.formValueMap..addAll({BirthDateValueKey: selectedDate})); - } - } - - void setDoYouLoveFood(String doYouLoveFood) { - this.setData( - this.formValueMap..addAll({DoYouLoveFoodValueKey: doYouLoveFood})); - } - - setEmailValidationMessage(String? validationMessage) => - this.fieldsValidationMessages[EmailValueKey] = validationMessage; - setPasswordValidationMessage(String? validationMessage) => - this.fieldsValidationMessages[PasswordValueKey] = validationMessage; - setShortBioValidationMessage(String? validationMessage) => - this.fieldsValidationMessages[ShortBioValueKey] = validationMessage; - setBirthDateValidationMessage(String? validationMessage) => - this.fieldsValidationMessages[BirthDateValueKey] = validationMessage; - setDoYouLoveFoodValidationMessage(String? validationMessage) => - this.fieldsValidationMessages[DoYouLoveFoodValueKey] = validationMessage; -} diff --git a/packages/stacked/example/lib/ui/form/validators.dart b/packages/stacked/example/lib/ui/form/validators.dart deleted file mode 100644 index a2f7a9d2c..000000000 --- a/packages/stacked/example/lib/ui/form/validators.dart +++ /dev/null @@ -1,6 +0,0 @@ -String? passwordValidator({String? value, int minimumLength = 6}) { - if (value != null && value.length < minimumLength) - return "Password should have min $minimumLength characters"; - else - return null; -} diff --git a/packages/stacked/example/lib/ui/home/home_view_provider_widget.dart b/packages/stacked/example/lib/ui/home/home_view_provider_widget.dart deleted file mode 100644 index 37ef35e30..000000000 --- a/packages/stacked/example/lib/ui/home/home_view_provider_widget.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:new_architecture/datamodels/human.dart'; -import 'package:new_architecture/ui/dumb_widgets/duplicate_name_widget.dart'; -import 'package:new_architecture/ui/dumb_widgets/full_name_widget.dart'; -import 'package:provider/provider.dart'; - -class HomeViewProviderWidget extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Scaffold( - body: Provider.value( - value: Human(name: 'Dane', surname: 'Mackier'), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [FullNameWidget(), DuplicateNameWidget()], - ), - ), - ); - } -} diff --git a/packages/stacked/example/lib/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart b/packages/stacked/example/lib/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart deleted file mode 100644 index 80ce06d19..000000000 --- a/packages/stacked/example/lib/ui/multiple_futures_example/multiple_futures_example_viewmodel.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:stacked/stacked.dart'; - -const String _NumberDelayFuture = 'delayedNumber'; -const String _StringDelayFuture = 'delayedString'; - -class MultipleFuturesExampleViewModel extends MultipleFutureViewModel { - int get fetchedNumber => dataMap![_NumberDelayFuture]; - String get fetchedString => dataMap![_StringDelayFuture]; - - bool get fetchingNumber => busy(_NumberDelayFuture); - bool get fetchingString => busy(_StringDelayFuture); - - @override - Map get futuresMap => { - _NumberDelayFuture: getNumberAfterDelay, - _StringDelayFuture: getStringAfterDelay, - }; - - Future getNumberAfterDelay() async { - await Future.delayed(Duration(seconds: 2)); - return 3; - } - - Future getStringAfterDelay() async { - await Future.delayed(Duration(seconds: 3)); - return 'String data'; - } -} diff --git a/packages/stacked/example/lib/ui/nonreactive/nonreactive_view.dart b/packages/stacked/example/lib/ui/nonreactive/nonreactive_view.dart deleted file mode 100644 index 92182e802..000000000 --- a/packages/stacked/example/lib/ui/nonreactive/nonreactive_view.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; - -import 'nonreactive_viewmodel.dart'; - -class NonReactiveView extends StatelessWidget { - const NonReactiveView({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ViewModelBuilder.nonReactive( - builder: (context, viewModel, child) => Scaffold( - floatingActionButton: FloatingActionButton( - onPressed: () { - viewModel.updateTitle(); - }, - ), - body: Center( - child: Text(viewModel.title), - ), - ), - viewModelBuilder: () => NonReactiveViewModel(), - ); - } -} diff --git a/packages/stacked/example/lib/ui/nonreactive/nonreactive_viewmodel.dart b/packages/stacked/example/lib/ui/nonreactive/nonreactive_viewmodel.dart deleted file mode 100644 index 505d47f9e..000000000 --- a/packages/stacked/example/lib/ui/nonreactive/nonreactive_viewmodel.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:stacked/stacked.dart'; - -class NonReactiveViewModel extends BaseViewModel { - String title = 'This should not change'; - - void updateTitle() { - title += '. This has changed'; - notifyListeners(); - } -} diff --git a/packages/stacked/example/lib/ui/stream_view/stream_counter_view.dart b/packages/stacked/example/lib/ui/stream_view/stream_counter_view.dart deleted file mode 100644 index 44b940ebe..000000000 --- a/packages/stacked/example/lib/ui/stream_view/stream_counter_view.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:new_architecture/ui/stream_view/stream_counter_viewmodel.dart'; -import 'package:stacked/stacked.dart'; - -class StreamCounterView extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - builder: (context, viewModel, child) => Scaffold( - body: Center( - child: Text(viewModel.title), - ), - floatingActionButton: MaterialButton( - child: Text('Change Stream Srouces'), - onPressed: viewModel.changeStreamSources, - ), - ), - viewModelBuilder: () => StreamCounterViewModel(), - ); - } -} diff --git a/packages/stacked/example/test/widget_test.dart b/packages/stacked/example/test/widget_test.dart deleted file mode 100644 index 3dd253abf..000000000 --- a/packages/stacked/example/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:new_architecture/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} diff --git a/packages/stacked/lib/src/code_generation/environment_filter.dart b/packages/stacked/lib/src/code_generation/environment_filter.dart deleted file mode 100644 index b310a18a4..000000000 --- a/packages/stacked/lib/src/code_generation/environment_filter.dart +++ /dev/null @@ -1,70 +0,0 @@ -/// a simple filter function to be used inside [SimpleEnvironmentFilter] -typedef EnvironmentFilterFunc = bool Function(Set); -const kEnvironmentsName = '__environments__'; - -/// filter for whether to register for the given set of environments -/// clients can extend this class to maker -/// their own environmentFilters -abstract class EnvironmentFilter { - /// holds passed environment keys - /// to be used inside the filter or - /// retrieved later by users - final Set environments; - - /// default constructor - const EnvironmentFilter(this.environments); - - /// This function is called before every - /// registration call, if it returns true, the dependency - /// will be registered otherwise, it will be ignored - bool canRegister(Set depEnvironments); -} - -/// A simple filter that can be used directly for simple use cases -/// without having to extend the base [EnvironmentFilter] -class SimpleEnvironmentFilter extends EnvironmentFilter { - final EnvironmentFilterFunc filter; - - const SimpleEnvironmentFilter( - {required this.filter, Set environments = const {}}) - : super(environments); - - @override - bool canRegister(Set depEnvironments) => filter(depEnvironments); -} - -/// This filter validates dependencies with no environment -/// keys or contain the provided [environment] -class NoEnvOrContains extends EnvironmentFilter { - NoEnvOrContains(String? environment) : super({environment}); - - @override - bool canRegister(Set depEnvironments) { - return (depEnvironments.isEmpty) || - depEnvironments.contains(environments.first); - } -} - -/// This filter validates dependencies with no environment -/// keys, or the ones containing all the provided [environments] -class NoEnvOrContainsAll extends EnvironmentFilter { - const NoEnvOrContainsAll(Set environments) : super(environments); - - @override - bool canRegister(Set depEnvironments) { - return (depEnvironments.isEmpty) || - depEnvironments.containsAll(environments); - } -} - -/// This filter validates dependencies with no environment -/// keys, or the ones containing one of the provided [environments] -class NoEnvOrContainsAny extends EnvironmentFilter { - const NoEnvOrContainsAny(Set environments) : super(environments); - - @override - bool canRegister(Set depEnvironments) { - return (depEnvironments.isEmpty) || - depEnvironments.intersection(environments).isNotEmpty; - } -} diff --git a/packages/stacked/lib/src/code_generation/forms/stacked_form_annotations.dart b/packages/stacked/lib/src/code_generation/forms/stacked_form_annotations.dart deleted file mode 100644 index 6235f2cff..000000000 --- a/packages/stacked/lib/src/code_generation/forms/stacked_form_annotations.dart +++ /dev/null @@ -1,47 +0,0 @@ -/// The annotation to be used for a view that contains a Form -class FormView { - /// The list of form fields to generate - final List? fields; - - const FormView({this.fields}); -} - -/// Describes a form field to be generated -class FormField { - /// The name of the form field. This will be used to generate the Key mapping - final String? name; - - const FormField({this.name}); -} - -/// Describes an entry field on the form that takes text -class FormTextField extends FormField { - /// Indicates if the [FormField] is a password field or not - final bool? isPassword; - - /// Assigns initial value, `text` parameter in `TextEditingController` - final String? initialValue; - - const FormTextField({ - String? name, - this.isPassword, - this.initialValue, - }) : super(name: name); -} - -/// Describes a date form field. -class FormDateField extends FormField { - const FormDateField({String? name}) : super(name: name); -} - -class FormDropdownField extends FormField { - final List items; - const FormDropdownField({String? name, required this.items}) - : super(name: name); -} - -class StaticDropdownItem { - final String title; - final String value; - const StaticDropdownItem({required this.title, required this.value}); -} diff --git a/packages/stacked/lib/src/code_generation/router/parameters.dart b/packages/stacked/lib/src/code_generation/router/parameters.dart deleted file mode 100644 index 20505a1df..000000000 --- a/packages/stacked/lib/src/code_generation/router/parameters.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:flutter/foundation.dart'; - -class Parameters { - final Map _params; - - Parameters(Map params) : _params = params; - - Map get rawMap => _params; - - ParameterValue operator [](String key) => ParameterValue._(_params[key]); - - @override - String toString() { - return _params.toString(); - } -} - -class ParameterValue { - final dynamic _value; - - const ParameterValue._(this._value); - - dynamic get value => _value; - - String? optString() => _value; - - String getString([String? defaultValue]) { - var val = _value ?? defaultValue; - if (val == null) { - throw FlutterError('Failed to parse [String] value from $_value'); - } - return val; - } - - int? optInt([int? defaultValue]) { - if (_value == null) { - return defaultValue; - } else if (_value is int) { - return _value; - } else { - return int.tryParse(_value.toString()) ?? defaultValue; - } - } - - int getInt([int? defaultValue]) { - var val = optInt(defaultValue); - if (val == null) { - throw FlutterError('Failed to parse [int] value from $_value'); - } - return val; - } - - double? optDouble([double? defaultValue]) { - if (_value == null) { - return defaultValue; - } else if (_value is double) { - return _value; - } else { - return double.tryParse(_value.toString()) ?? defaultValue; - } - } - - double getDouble([double? defaultValue]) { - var val = optDouble(defaultValue); - if (val == null) { - throw FlutterError('Failed to parse [double] value from $_value'); - } - return val; - } - - num? optNum([num? defaultValue]) { - if (_value == null) { - return defaultValue; - } else if (_value is num) { - return _value; - } else { - return double.tryParse(_value.toString()) ?? defaultValue; - } - } - - num getNum([num? defaultValue]) { - var val = optNum(defaultValue); - if (val == null) { - throw FlutterError('Failed to parse [num] value from $_value'); - } - return val; - } - - bool? optBool([bool? defaultValue]) { - switch (_value?.toLowerCase()) { - case 'true': - return true; - case 'false': - return false; - default: - return defaultValue; - } - } - - bool getBool([bool? defaultValue]) { - var val = optBool(defaultValue); - if (val == null) { - throw FlutterError('Failed to parse [bool] value from $_value'); - } - return val; - } -} diff --git a/packages/stacked/lib/src/code_generation/router/route_guard.dart b/packages/stacked/lib/src/code_generation/router/route_guard.dart deleted file mode 100644 index 5c1d9996a..000000000 --- a/packages/stacked/lib/src/code_generation/router/route_guard.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'dart:async'; -import 'extended_navigator.dart'; - -abstract class RouteGuard { - Future canNavigate( - ExtendedNavigatorState navigator, - String routeName, - Object? arguments, - ); -} diff --git a/packages/stacked/lib/src/code_generation/stacked_app.dart b/packages/stacked/lib/src/code_generation/stacked_app.dart deleted file mode 100644 index 8ce79747e..000000000 --- a/packages/stacked/lib/src/code_generation/stacked_app.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:stacked/src/code_generation/stacked_locator_annotations.dart'; -import 'package:stacked/src/code_generation/stacked_logger_annotations.dart'; -import 'package:stacked/src/code_generation/stacked_router_annotations.dart'; - -/// The annotation that defines a stacked application -class StackedApp { - /// Supply to the StackedApp annotation a locatorName. The default name will be "locator". - final String locatorName; - - /// Supply the StackedApp annotation with a locatorSetupName. The default name will be setupLocator - final String locatorSetupName; - - /// Defines all the routes that has to be generated for the onGenerateRoute function - final List routes; - - /// Defines a list of services to be registered on the locator for use in the app - final List? dependencies; - - /// Defines the logger to be generated in the app - final StackedLogger? logger; - - const StackedApp({ - required this.routes, - this.logger, - this.dependencies, - this.locatorName = 'locator', - this.locatorSetupName = 'setupLocator', - }); -} diff --git a/packages/stacked/lib/src/code_generation/stacked_locator.dart b/packages/stacked/lib/src/code_generation/stacked_locator.dart deleted file mode 100644 index 4172be73d..000000000 --- a/packages/stacked/lib/src/code_generation/stacked_locator.dart +++ /dev/null @@ -1,505 +0,0 @@ -import 'package:get_it/get_it.dart'; - -import '../../stacked_annotations.dart'; - -/// A thin wrapper around get_it to reduce the number of direct dependencies the user has to depend on. -class StackedLocator { - static StackedLocator? _instance; - - GetIt locator; - - EnvironmentFilter? _environmentFilter; - - StackedLocator._(GetIt instance) : locator = instance { - registerEnvironment(); - } - - /// access to the Singleton instance of GetIt - static StackedLocator get instance { - // ignore: join_return_with_assignment - if (_instance == null) { - // TODO: Add new instance ability here - _instance = StackedLocator._(GetIt.instance); - } - - return _instance!; - } - - factory StackedLocator.asNewInstance() { - return StackedLocator._(GetIt.asNewInstance()); - } - - void registerEnvironment({ - String? environment, - EnvironmentFilter? environmentFilter, - }) { - assert(environmentFilter == null || environment == null); - _environmentFilter = environmentFilter ?? NoEnvOrContains(environment); - - removeRegistrationIfExists>(instanceName: kEnvironmentsName); - - locator.registerLazySingleton>( - () => _environmentFilter!.environments, - instanceName: kEnvironmentsName, - ); - } - - bool _canRegister(Set? registerFor) { - return _environmentFilter!.canRegister(registerFor ?? {}); - } - - /// If you need more than one instance of GetIt you can use [asNewInstance()] - /// You should prefer to use the `instance()` method to access the global instance of [GetIt]. - // factory StackedLocator.asNewInstance() { - // return StackedLocator(GetIt.asNewInstance()); - // } - - /// By default it's not allowed to register a type a second time. - /// If you really need to you can disable the asserts by setting[allowReassignment]= true - bool allowReassignment = false; - - /// retrieves or creates an instance of a registered type [T] depending on the registration - /// function used for this type or based on a name. - /// for factories you can pass up to 2 parameters [param1,param2] they have to match the types - /// given at registration with [registerFactoryParam()] - T get( - {String? instanceName, dynamic param1, dynamic param2}) => - locator.get( - instanceName: instanceName, - param1: param1, - param2: param2, - ); - - /// Returns an Future of an instance that is created by an async factory or a Singleton that is - /// not ready with its initialization. - /// for async factories you can pass up to 2 parameters [param1,param2] they have to match the types - /// given at registration with [registerFactoryParamAsync()] - Future getAsync( - {String? instanceName, dynamic param1, dynamic param2}) => - locator.getAsync( - instanceName: instanceName, - param1: param1, - param2: param2, - ); - - /// Callable class so that you can write `GetIt.instance` instead of - /// `GetIt.instance.get` - T call( - {String? instanceName, dynamic param1, dynamic param2}) => - locator( - instanceName: instanceName, - param1: param1, - param2: param2, - ); - - /// registers a type so that a new instance will be created on each call of [get] on that type - /// [T] type to register - /// [factoryfunc] factory function for this type - /// [instanceName] if you provide a value here your factory gets registered with that - /// name instead of a type. This should only be necessary if you need to register more - /// than one instance of one type. Its highly not recommended - void registerFactory( - FactoryFunc factoryfunc, { - String? instanceName, - Set? registerFor, - }) { - if (_canRegister(registerFor)) { - locator.registerFactory( - factoryfunc, - instanceName: instanceName, - ); - } - } - - /// registers a type so that a new instance will be created on each call of [get] on that type based on - /// up to two parameters provided to [get()] - /// [T] type to register - /// [P1] type of param1 - /// [P2] type of param2 - /// if you use only one parameter pass void here - /// [factoryfunc] factory function for this type that accepts two parameters - /// [instanceName] if you provide a value here your factory gets registered with that - /// name instead of a type. This should only be necessary if you need to register more - /// than one instance of one type. Its highly not recommended - /// - /// example: - /// getIt.registerFactoryParam((s,i) - /// => TestClassParam(param1:s, param2: i)); - /// - /// if you only use one parameter: - /// - /// getIt.registerFactoryParam((s,_) - /// => TestClassParam(param1:s); - void registerFactoryParam( - FactoryFuncParam factoryfunc, { - String? instanceName, - Set? registerFor, - }) { - if (_canRegister(registerFor)) { - locator.registerFactoryParam( - factoryfunc, - instanceName: instanceName, - ); - } - } - - /// Registers a type so that a new instance will be created on each call of [getAsync] on that type - /// the creation function is executed asynchronously and has to be accessed with [getAsync] - /// [T] type to register - /// [factoryfunc] async factory function for this type - /// [instanceName] if you provide a value here your factory gets registered with that - /// name instead of a type. This should only be necessary if you need to register more - /// than one instance of one type. Its highly not recommended - void registerFactoryAsync( - FactoryFuncAsync factoryfunc, { - String? instanceName, - Set? registerFor, - }) { - if (_canRegister(registerFor)) { - locator.registerFactoryAsync( - factoryfunc, - instanceName: instanceName, - ); - } - } - - /// registers a type so that a new instance will be created on each call of [getAsync] - /// on that type based on up to two parameters provided to [getAsync()] - /// the creation function is executed asynchronously and has to be accessed with [getAsync] - /// [T] type to register - /// [P1] type of param1 - /// [P2] type of param2 - /// if you use only one parameter pass void here - /// [factoryfunc] factory function for this type that accepts two parameters - /// [instanceName] if you provide a value here your factory gets registered with that - /// name instead of a type. This should only be necessary if you need to register more - /// than one instance of one type. Its highly not recommended - /// - /// example: - /// getIt.registerFactoryParam((s,i) async - /// => TestClassParam(param1:s, param2: i)); - /// - /// if you only use one parameter: - /// - /// getIt.registerFactoryParam((s,_) async - /// => TestClassParam(param1:s); - void registerFactoryParamAsync( - FactoryFuncParamAsync factoryfunc, { - String? instanceName, - Set? registerFor, - }) { - if (_canRegister(registerFor)) { - locator.registerFactoryParamAsync( - factoryfunc, - instanceName: instanceName, - ); - } - } - - /// registers a type as Singleton by passing an [instance] of that type - /// that will be returned on each call of [get] on that type - /// [T] type to register - /// [instanceName] if you provide a value here your instance gets registered with that - /// name instead of a type. This should only be necessary if you need to register more - /// than one instance of one type. Its highly not recommended - /// If [signalsReady] is set to `true` it means that the future you can get from `allReady()` - /// cannot complete until this this instance was signalled ready by calling [signalsReady(instance)]. - void registerSingleton( - T instance, { - String? instanceName, - bool? signalsReady, - DisposingFunc? dispose, - Set? registerFor, - }) { - if (_canRegister(registerFor)) { - locator.registerSingleton( - instance, - instanceName: instanceName, - signalsReady: signalsReady, - dispose: dispose, - ); - } - } - - /// registers a type as Singleton by passing an factory function of that type - /// that will be called on each call of [get] on that type - /// [T] type to register - /// [instanceName] if you provide a value here your instance gets registered with that - /// name instead of a type. This should only be necessary if you need to register more - /// than one instance of one type. Its highly not recommended - /// [dependsOn] if this instance depends on other registered Singletons before it can be initilaized - /// you can either orchestrate this manually using [isReady()] or pass a list of the types that the - /// instance depends on here. [factoryFunc] won't get executed till this types are ready. - /// [func] is called - /// If [signalsReady] is set to `true` it means that the future you can get from `allReady()` - /// cannot complete until this this instance was signalled ready by calling [signalsReady(instance)]. - void registerSingletonWithDependencies( - FactoryFunc factoryFunc, { - String? instanceName, - Iterable? dependsOn, - bool? signalsReady, - DisposingFunc? dispose, - Set? registerFor, - }) { - if (_canRegister(registerFor)) { - locator.registerSingletonWithDependencies( - factoryFunc, - instanceName: instanceName, - dependsOn: dependsOn, - signalsReady: signalsReady, - dispose: dispose, - ); - } - } - - /// registers a type as Singleton by passing an asynchronous factory function which has to return the instance - /// that will be returned on each call of [get] on that type. - /// Therefore you have to ensure that the instance is ready before you use [get] on it or use [getAsync()] to - /// wait for the completion. - /// You can wait/check if the instance is ready by using [isReady()] and [isReadySync()]. - /// [factoryfunc] is executed immediately if there are no dependencies to other Singletons (see below). - /// As soon as it returns, this instance is marked as ready unless you don't set [signalsReady==true] - /// [instanceName] if you provide a value here your instance gets registered with that - /// name instead of a type. This should only be necessary if you need to register more - /// than one instance of one type. Its highly not recommended - /// [dependsOn] if this instance depends on other registered Singletons before it can be initilaized - /// you can either orchestrate this manually using [isReady()] or pass a list of the types that the - /// instance depends on here. [factoryFunc] won't get executed till this types are ready. - /// If [signalsReady] is set to `true` it means that the future you can get from `allReady()` cannot complete until this - /// this instance was signalled ready by calling [signalsReady(instance)]. In that case no automatic ready signal - /// is made after completion of [factoryfunc] - void registerSingletonAsync( - FactoryFuncAsync factoryfunc, { - String? instanceName, - Iterable? dependsOn, - bool? signalsReady, - DisposingFunc? dispose, - Set? registerFor, - }) { - if (_canRegister(registerFor)) { - locator.registerSingletonAsync( - factoryfunc, - instanceName: instanceName, - dependsOn: dependsOn, - signalsReady: signalsReady, - dispose: dispose, - ); - } - } - - /// registers a type as Singleton by passing a factory function that will be called - /// on the first call of [get] on that type - /// [T] type to register - /// [factoryfunc] factory function for this type - /// [instanceName] if you provide a value here your factory gets registered with that - /// name instead of a type. This should only be necessary if you need to register more - /// than one instance of one type. Its highly not recommended - /// [registerLazySingleton] does not influence [allReady] however you can wait - /// for and be dependent on a LazySingleton. - void registerLazySingleton( - FactoryFunc factoryfunc, { - String? instanceName, - DisposingFunc? dispose, - Set? registerFor, - }) { - if (_canRegister(registerFor)) { - locator.registerLazySingleton( - factoryfunc, - instanceName: instanceName, - dispose: dispose, - ); - } - } - - /// registers a type as Singleton by passing a async factory function that will be called - /// on the first call of [getAsnc] on that type - /// This is a rather esoteric requirement so you should seldom have the need to use it. - /// This factory function [factoryFunc] isn't called immediately but wait till the first call by - /// [getAsync()] or [isReady()] is made - /// To control if an async Singleton has completed its [factoryFunc] gets a `Completer` passed - /// as parameter that has to be completed to signal that this instance is ready. - /// Therefore you have to ensure that the instance is ready before you use [get] on it or use [getAsync()] to - /// wait for the completion. - /// You can wait/check if the instance is ready by using [isReady()] and [isReadySync()]. - /// [instanceName] if you provide a value here your instance gets registered with that - /// name instead of a type. This should only be necessary if you need to register more - /// than one instance of one type. Its highly not recommended. - /// [registerLazySingletonAsync] does not influence [allReady] however you can wait - /// for and be dependent on a LazySingleton. - void registerLazySingletonAsync( - FactoryFuncAsync factoryFunc, { - String? instanceName, - DisposingFunc? dispose, - Set? registerFor, - }) { - if (_canRegister(registerFor)) { - locator.registerLazySingletonAsync( - factoryFunc, - instanceName: instanceName, - dispose: dispose, - ); - } - } - - /// Tests if an [instance] of an object or aType [T] or a name [instanceName] - /// is registered inside GetIt - bool isRegistered( - {Object? instance, String? instanceName}) => - locator.isRegistered( - instance: instance, - instanceName: instanceName, - ); - - /// Clears all registered types. Handy when writing unit tests - /// If you provided dispose function when registering they will be called - /// [dispose] if `false` it only resets without calling any dispose - /// functions - /// As dispose funcions can be async, you should await this function. - Future reset({bool dispose = true}) => locator.reset(dispose: dispose); - - /// Clears all registered types for the current scope - /// If you provided dispose function when registering they will be called - /// [dispose] if `false` it only resets without calling any dispose - /// functions - /// As dispose funcions can be async, you should await this function. - Future resetScope({bool dispose = true}) => - locator.resetScope(dispose: dispose); - - /// Creates a new registration scope. If you register types after creating - /// a new scope they will hide any previous registration of the same type. - /// Scopes allow you to manage different live times of your Objects. - /// [scopeName] if you name a scope you can pop all scopes above the named one - /// by using the name. - /// [dispose] function that will be called when you pop this scope. The scope - /// is still valied while it is executed - void pushNewScope({String? scopeName, ScopeDisposeFunc? dispose}) => - locator.pushNewScope( - scopeName: scopeName, - dispose: dispose, - ); - - /// Disposes all factories/Singletons that have ben registered in this scope - /// and pops (destroys) the scope so that the previous scope gets active again. - /// if you provided dispose functions on registration, they will be called. - /// if you passed a dispose function when you pushed this scope it will be - /// calles before the scope is popped. - /// As dispose funcions can be async, you should await this function. - Future popScope() => locator.popScope(); - - /// if you have a lot of scopes with names you can pop (see [popScope]) all - /// scopes above the scope with [name] including that scope - /// Scopes are poped in order from the top - /// As dispose funcions can be async, you should await this function. - /// it no scope with [name] exists, nothing is popped and `false` is returned - Future popScopesTill(String name) => locator.popScopesTill(name); - - /// Clears the instance of a lazy singleton, - /// being able to call the factory function on the next call - /// of [get] on that type again. - /// you select the lazy Singleton you want to reset by either providing - /// an [instance], its registered type [T] or its registration name. - /// if you need to dispose some resources before the reset, you can - /// provide a [disposingFunction]. This function overrides the disposing - /// you might have provided when registering. - void resetLazySingleton( - {Object? instance, - String? instanceName, - void Function(T)? disposingFunction}) => - locator.resetLazySingleton( - instance: instance, - instanceName: instanceName, - disposingFunction: disposingFunction, - ); - - /// Unregister an [instance] of an object or a factory/singleton by Type [T] or by name [instanceName] - /// if you need to dispose any resources you can do it using [disposingFunction] function - /// that provides a instance of your class to be disposed. This function overrides the disposing - /// you might have provided when registering. - void unregister( - {Object? instance, - String? instanceName, - void Function(T)? disposingFunction}) => - locator.unregister( - instance: instance, - instanceName: instanceName, - disposingFunction: disposingFunction, - ); - - /// returns a Future that completes if all asynchronously created Singletons and any Singleton that had - /// [signalsReady==true] are ready. - /// This can be used inside a FutureBuilder to change the UI as soon as all initialization - /// is done - /// If you pass a [timeout], an [WaitingTimeOutException] will be thrown if not all Singletons - /// were ready in the given time. The Exception contains details on which Singletons are not ready yet. - /// if [allReady] should not wait for the completion of async Signletons set - /// [ignorePendingAsyncCreation==true] - Future allReady( - {Duration? timeout, bool ignorePendingAsyncCreation = false}) => - locator.allReady( - timeout: timeout, - ignorePendingAsyncCreation: ignorePendingAsyncCreation, - ); - - /// Returns a Future that completes if the instance of an Singleton, defined by Type [T] or - /// by name [instanceName] or by passing the an existing [instance], is ready - /// If you pass a [timeout], an [WaitingTimeOutException] will be thrown if the instance - /// is not ready in the given time. The Exception contains details on which Singletons are - /// not ready at that time. - /// [callee] optional parameter which makes debugging easier. Pass `this` in here. - Future isReady({ - Object? instance, - String? instanceName, - Duration? timeout, - Object? callee, - }) => - locator.isReady( - instance: instance, - instanceName: instanceName, - timeout: timeout, - callee: callee, - ); - - /// Checks if an async Singleton defined by an [instance], a type [T] or an [instanceName] - /// is ready without waiting - bool isReadySync( - {Object? instance, String? instanceName}) => - locator.isReadySync( - instance: instance, - instanceName: instanceName, - ); - - /// Returns if all async Singletons are ready without waiting - /// if [allReady] should not wait for the completion of async Signletons set - /// [ignorePendingAsyncCreation==true] - // ignore: avoid_positional_boolean_parameters - bool allReadySync([bool ignorePendingAsyncCreation = false]) => - locator.allReadySync(ignorePendingAsyncCreation); - - /// Used to manually signal the ready state of a Singleton. - /// If you want to use this mechanism you have to pass [signalsReady==true] when registering - /// the Singleton. - /// If [instance] has a value GetIt will search for the responsible Singleton - /// and completes all futures that might be waited for by [isReady] - /// If all waiting singletons have signalled ready the future you can get - /// from [allReady] is automatically completed - /// - /// Typically this is used in this way inside the registered objects init - /// method `GetIt.instance.signalReady(this);` - /// - /// if [instance] is `null` and no factory/singleton is waiting to be signalled this - /// will complete the future you got from [allReady], so it can be used to globally - /// giving a ready Signal - /// - /// Both ways are mutual exclusive, meaning either only use the global `signalReady()` and - /// don't register a singleton to signal ready or use any async registrations - /// - /// Or use async registrations methods or let individual instances signal their ready - /// state on their own. - void signalReady(Object instance) => locator.signalReady(instance); - - void removeRegistrationIfExists({String? instanceName}) { - if (locator.isRegistered(instanceName: instanceName)) { - locator.unregister(instanceName: instanceName); - } - } -} diff --git a/packages/stacked/lib/src/code_generation/stacked_locator_annotations.dart b/packages/stacked/lib/src/code_generation/stacked_locator_annotations.dart deleted file mode 100644 index b51eb6026..000000000 --- a/packages/stacked/lib/src/code_generation/stacked_locator_annotations.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'dart:async'; - -/// The class to describe a service registration on the get_it locator - -class Environment { - const Environment._(); - - /// preset of common env name 'dev' - static const dev = 'dev'; - - /// preset of common env name 'prod' - static const prod = 'prod'; - - /// preset of common env name 'test' - static const test = 'test'; -} - -class DependencyRegistration { - /// The type of the service to register - final Type? classType; - - /// An abstracted class type of service to register - final Type? asType; - - final Function? resolveUsing; - - final Set? environments; - final Function? dispose; - final Type? param1; - final Type? param2; - - const DependencyRegistration({ - this.environments, - this.classType, - this.asType, - this.resolveUsing, - this.dispose, - this.param1, - this.param2, - }); -} - -/// Registers the type passed in as a singleton instance in the get_it locator -class Singleton extends DependencyRegistration { - const Singleton({ - Type? classType, - Type? asType, - Function? resolveUsing, - Set? environments, - }) : super( - classType: classType, - asType: asType, - resolveUsing: resolveUsing, - environments: environments, - ); -} - -/// Registers the type passed in as a LazySingleton instance in the get_it locator -class LazySingleton extends DependencyRegistration { - const LazySingleton({ - Type? classType, - Type? asType, - Function? resolveUsing, - Set? environments, - FutureOr Function(T)? dispose, - }) : super( - classType: classType, - asType: asType, - resolveUsing: resolveUsing, - environments: environments, - ); -} - -/// Registers the type passed in as a Factory in the get_it locator -class Factory extends DependencyRegistration { - const Factory({ - Type? classType, - Type? asType, - Set? environments, - }) : super( - classType: classType, - asType: asType, - environments: environments, - ); -} - -/// Registers the type passed in as a Factory in the get_it locator -class FactoryWithParam extends DependencyRegistration { - const FactoryWithParam({ - Type? asType, - Type? classType, - Set? environments, - }) : super( - asType: asType, - classType: classType, - environments: environments, - ); -} - -/// Marks a constructor param as -/// factoryParam so it can be passed -/// to the resolver function -class FactoryParam { - const FactoryParam._(); -} - -/// const instance of [FactoryParam] -/// with default arguments -const factoryParam = FactoryParam._(); - -/// Registers the type passed in to be presolved using the function passed in -class Presolve extends DependencyRegistration { - /// The static instance Future function to use for resolving the type registered - final Future Function()? presolveUsing; - - const Presolve({ - Type? classType, - Type? asType, - this.presolveUsing, - Set? environments, - }) : super(classType: classType, asType: asType, environments: environments); -} diff --git a/packages/stacked/lib/src/code_generation/stacked_logger_annotations.dart b/packages/stacked/lib/src/code_generation/stacked_logger_annotations.dart deleted file mode 100644 index e87d24772..000000000 --- a/packages/stacked/lib/src/code_generation/stacked_logger_annotations.dart +++ /dev/null @@ -1,19 +0,0 @@ -/// Defined the config for the logger in the app -class StackedLogger { - final String logHelperName; - - /// Pass loggerOutputs types here.[StackedLogger(loggerOutputs: [CrashlyticsOutput])] - /// - /// loggerOutputs must extends [LogOutput] - final List loggerOutputs; - - /// When set to true console logs will not be printed in release mode - /// Default is true - final bool disableReleaseConsoleOutput; - - const StackedLogger({ - this.loggerOutputs = const [], - this.logHelperName = 'getLogger', - this.disableReleaseConsoleOutput = true, - }); -} diff --git a/packages/stacked/lib/src/code_generation/stacked_router_annotations.dart b/packages/stacked/lib/src/code_generation/stacked_router_annotations.dart deleted file mode 100644 index 43139d5e9..000000000 --- a/packages/stacked/lib/src/code_generation/stacked_router_annotations.dart +++ /dev/null @@ -1,293 +0,0 @@ -class StackedRouterAnnotation { - // if true a Navigator extension will be generated with - // helper push methods of all routes - final bool? generateNavigationHelperExtension; - - // defaults to 'Routes' - final String? routesClassName; - - //This is the prefix for each Route String that is generated - // initial routes will always be named '/' - // defaults to '/' - final String? routePrefix; - - final List routes; - - const StackedRouterAnnotation._( - this.generateNavigationHelperExtension, - this.routesClassName, - this.routePrefix, - this.routes, - ) : assert(routes != null); -} - -// Defaults created routes to MaterialPageRoute unless -// overridden by AutoRoute annotation -class MaterialRouter extends StackedRouterAnnotation { - const MaterialRouter({ - bool? generateNavigationHelperExtension, - String? routesClassName, - String? pathPrefix, - required List routes, - }) : super._(generateNavigationHelperExtension, routesClassName, pathPrefix, - routes); -} - -// Defaults created routes to CupertinoPageRoute unless -// overridden by AutoRoute annotation -class CupertinoRouter extends StackedRouterAnnotation { - const CupertinoRouter({ - bool? generateNavigationHelperExtension, - String? routesClassName, - String? pathPrefix, - required List routes, - }) : super._( - generateNavigationHelperExtension, - routesClassName, - pathPrefix, - routes, - ); -} - -class AdaptiveRouter extends StackedRouterAnnotation { - const AdaptiveRouter({ - bool? generateNavigationHelperExtension, - String? routesClassName, - String? pathPrefix, - required List routes, - }) : super._( - generateNavigationHelperExtension, - routesClassName, - pathPrefix, - routes, - ); -} - -/// Defaults created routes to PageRouteBuilder unless -/// overridden by AutoRoute annotation -class CustomRouter extends StackedRouterAnnotation { - /// this builder function is passed to the transition builder - /// function in [PageRouteBuilder] - /// - /// I couldn't type this function from here but it should match - /// typedef [RouteTransitionsBuilder] = Widget Function(BuildContext context, Animation animation, - /// Animation secondaryAnimation, Widget child); - /// - /// you should only reference the function so - /// the generator can import it into router_base.dart - final Function? transitionsBuilder; - - /// route transition duration in milliseconds - /// is passed to [PageRouteBuilder] - /// this property is ignored unless a [transitionBuilder] is provided - final int? durationInMilliseconds; - - /// route transition reverse duration in milliseconds - /// is passed to [PageRouteBuilder] - final int? reverseDurationInMilliseconds; - - /// passed to the opaque property in [PageRouteBuilder] - final bool? opaque; - - /// passed to the barrierDismissible property in [PageRouteBuilder] - final bool? barrierDismissible; - - const CustomRouter( - {this.transitionsBuilder, - this.barrierDismissible, - this.durationInMilliseconds, - this.reverseDurationInMilliseconds, - this.opaque, - bool? generateNavigationHelperExtension, - String? routesClassName, - String? pathPrefix, - required List routes}) - : super._( - generateNavigationHelperExtension, - routesClassName, - pathPrefix, - routes, - ); -} - -/// [T] is the results type returned -/// from this page route MaterialPageRoute() -/// defaults to dynamic - -class StackedRoute { - // initial route will have an explicit name of "/" - // there could be only one initial route per navigator. - final bool? initial; - - /// passed to the fullscreenDialog property in [MaterialPageRoute] - final bool? fullscreenDialog; - - /// passed to the maintainState property in [MaterialPageRoute] - final bool? maintainState; - final List? children; - - /// route path name which will be assigned to the given variable name - /// const homeScreen = '[path]'; - /// if null a kabab cased variable name - /// prefixed with '/' will be used; - /// homeScreen -> home-screen - - final String? path; - final String? name; - - final Type page; - - final List? guards; - - const StackedRoute( - {required this.page, - this.initial, - this.guards, - this.fullscreenDialog, - this.maintainState, - this.path, - this.name, - this.children}); -} - -class MaterialRoute extends StackedRoute { - const MaterialRoute( - {String? path, - required Type page, - bool? initial, - bool? fullscreenDialog, - bool? maintainState, - String? name, - List? guards, - List? children}) - : super( - page: page, - guards: guards, - initial: initial, - fullscreenDialog: fullscreenDialog, - maintainState: maintainState, - path: path, - children: children, - name: name, - ); -} - -// forces usage of CupertinoPageRoute instead of MaterialPageRoute -class CupertinoRoute extends StackedRoute { - /// passed to the title property in [CupertinoPageRoute] - final String? title; - - const CupertinoRoute( - {bool? initial, - bool? fullscreenDialog, - bool? maintainState, - String? path, - this.title, - String? name, - required Type page, - List? guards, - List? children}) - : super( - initial: initial, - fullscreenDialog: fullscreenDialog, - maintainState: maintainState, - path: path, - name: name, - page: page, - guards: guards, - children: children); -} - -class AdaptiveRoute extends StackedRoute { - const AdaptiveRoute( - {bool? initial, - bool? fullscreenDialog, - bool? maintainState, - String? name, - String? path, - Type? returnType, - this.cupertinoPageTitle, - required Type page, - List? guards, - List? children}) - : super( - initial: initial, - fullscreenDialog: fullscreenDialog, - maintainState: maintainState, - path: path, - name: name, - page: page, - guards: guards, - children: children); - - /// passed to the title property in [CupertinoPageRoute] - final String? cupertinoPageTitle; -} - -class CustomRoute extends StackedRoute { - /// this builder function is passed to the transition builder - /// function in [PageRouteBuilder] - /// - /// I couldn't type this function from here but it should match - /// typedef [RouteTransitionsBuilder] = Widget Function(BuildContext context, Animation animation, - /// Animation secondaryAnimation, Widget child); - /// - /// you should only reference the function so - /// the generator can import it into router_base.dart - final Function? transitionsBuilder; - - /// route transition duration in milliseconds - /// is passed to [PageRouteBuilder] - /// this property is ignored unless a [transitionBuilder] is provided - final int? durationInMilliseconds; - - /// route transition reverse duration in milliseconds - /// is passed to [PageRouteBuilder] - final int? reverseDurationInMilliseconds; - - /// passed to the opaque property in [PageRouteBuilder] - final bool? opaque; - - /// passed to the barrierDismissible property in [PageRouteBuilder] - final bool? barrierDismissible; - - const CustomRoute({ - bool? initial, - bool? fullscreenDialog, - bool? maintainState, - String? name, - String? path, - required Type page, - List? guards, - List? children, - this.transitionsBuilder, - this.durationInMilliseconds, - this.reverseDurationInMilliseconds, - this.opaque, - this.barrierDismissible, - }) : super( - initial: initial, - fullscreenDialog: fullscreenDialog, - maintainState: maintainState, - path: path, - name: name, - page: page, - guards: guards, - children: children); -} - -class PathParam { - final String? name; - - const PathParam([this.name]); -} - -const pathParam = PathParam(); - -class QueryParam { - final String? name; - - const QueryParam([this.name]); -} - -const queryParam = QueryParam(); diff --git a/packages/stacked/lib/src/reactive/type_def.dart b/packages/stacked/lib/src/reactive/type_def.dart deleted file mode 100644 index fa2e9c2c2..000000000 --- a/packages/stacked/lib/src/reactive/type_def.dart +++ /dev/null @@ -1,12 +0,0 @@ -typedef void ValueCallback(T v); - -/// A callback with no arguments. -/// -/// Intended to listen to events emitted by [Emitter]. -typedef dynamic Callback(); - -typedef bool Condition(); - -typedef T ValueGetter(); - -typedef void ValueSetter(T val); diff --git a/packages/stacked/lib/src/state_management/base_view_models.dart b/packages/stacked/lib/src/state_management/base_view_models.dart deleted file mode 100644 index 2d1b0e744..000000000 --- a/packages/stacked/lib/src/state_management/base_view_models.dart +++ /dev/null @@ -1,611 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/cupertino.dart'; -import 'package:stacked/src/state_management/reactive_service_mixin.dart'; - -/// Contains ViewModel functionality for busy state management -class BaseViewModel extends ChangeNotifier { - Map _busyStates = Map(); - Map _errorStates = Map(); - - bool _initialised = false; - bool get initialised => _initialised; - - bool _onModelReadyCalled = false; - bool get onModelReadyCalled => _onModelReadyCalled; - - bool _disposed = false; - bool get disposed => _disposed; - - /// Returns the busy status for an object if it exists. Returns false if not present - bool busy(Object? object) => _busyStates[object.hashCode] ?? false; - - dynamic error(Object object) => _errorStates[object.hashCode]; - - /// Returns the busy status of the ViewModel - bool get isBusy => busy(this); - - /// Returns the error status of the ViewModel - bool get hasError => error(this) != null; - - /// Returns the error status of the ViewModel - dynamic get modelError => error(this); - - // Returns true if any objects still have a busy status that is true. - bool get anyObjectsBusy => _busyStates.values.any((busy) => busy); - - /// Marks the ViewModel as busy and calls notify listeners - void setBusy(bool value) { - setBusyForObject(this, value); - } - - /// Sets the error for the ViewModel - void setError(dynamic error) { - setErrorForObject(this, error); - } - - /// returns real data passed if neither the model is busy nor the object passed is busy - T skeletonData( - {required T? realData, required T busyData, Object? busyKey}) { - /// If busyKey is supplied we check busy(busyKey) to see if that property is busy - /// If it is we return busyData, else realData - bool isBusyKeySupplied = busyKey != null; - if ((isBusyKeySupplied && busy(busyKey)) || realData == null) - return busyData; - else if (!isBusyKeySupplied && isBusy) return busyData; - - return realData; - } - - /// Returns a boolean that indicates if the ViewModel has an error for the key - bool hasErrorForKey(Object key) => error(key) != null; - - /// Clears all the errors - void clearErrors() { - _errorStates.clear(); - } - - /// Sets the busy state for the object equal to the value passed in and notifies Listeners - /// If you're using a primitive type the value SHOULD NOT BE CHANGED, since Hashcode uses == value - void setBusyForObject(Object? object, bool value) { - _busyStates[object.hashCode] = value; - notifyListeners(); - } - - /// Sets the error state for the object equal to the value passed in and notifies Listeners - /// If you're using a primitive type the value SHOULD NOT BE CHANGED, since Hashcode uses == value - void setErrorForObject(Object object, dynamic value) { - _errorStates[object.hashCode] = value; - notifyListeners(); - } - - /// Function that is called when a future throws an error - void onFutureError(dynamic error, Object? key) {} - - /// Sets the ViewModel to busy, runs the future and then sets it to not busy when complete. - /// - /// rethrows [Exception] after setting busy to false for object or class - Future runBusyFuture(Future busyFuture, - {Object? busyObject, bool throwException = false}) async { - _setBusyForModelOrObject(true, busyObject: busyObject); - try { - var value = await runErrorFuture(busyFuture, - key: busyObject, throwException: throwException); - return value; - } catch (e) { - if (throwException) rethrow; - return Future.value(); - } finally { - _setBusyForModelOrObject(false, busyObject: busyObject); - } - } - - Future runErrorFuture(Future future, - {Object? key, bool throwException = false}) async { - try { - _setErrorForModelOrObject(null, key: key); - return await future; - } catch (e) { - _setErrorForModelOrObject(e, key: key); - onFutureError(e, key); - if (throwException) rethrow; - return Future.value(); - } - } - - /// Sets the initialised value for the ViewModel to true. This is called after - /// the first initialise special ViewModel call - void setInitialised(bool value) { - _initialised = value; - } - - /// Sets the onModelReadyCalled value to true. This is called after this first onModelReady call - void setOnModelReadyCalled(bool value) { - _onModelReadyCalled = value; - } - - void _setBusyForModelOrObject(bool value, {Object? busyObject}) { - if (busyObject != null) { - setBusyForObject(busyObject, value); - } else { - setBusyForObject(this, value); - } - } - - void _setErrorForModelOrObject(dynamic value, {Object? key}) { - if (key != null) { - setErrorForObject(key, value); - } else { - setErrorForObject(this, value); - } - } - - // Sets up streamData property to hold data, busy, and lifecycle events - @protected - StreamData setupStream( - Stream stream, { - onData, - onSubscribed, - onError, - onCancel, - transformData, - }) { - StreamData streamData = StreamData( - stream, - onData: onData, - onSubscribed: onSubscribed, - onError: onError, - onCancel: onCancel, - transformData: transformData, - ); - streamData.initialise(); - - return streamData; - } - - @override - void notifyListeners() { - if (!disposed) { - super.notifyListeners(); - } - } - - @override - void dispose() { - _disposed = true; - super.dispose(); - } -} - -/// A [BaseViewModel] that provides functionality to subscribe to a reactive service. -abstract class ReactiveViewModel extends BaseViewModel { - late List _reactiveServices; - - List get reactiveServices; - - ReactiveViewModel() { - _reactToServices(reactiveServices); - } - - void _reactToServices(List reactiveServices) { - _reactiveServices = reactiveServices; - for (var reactiveService in _reactiveServices) { - reactiveService.addListener(_indicateChange); - } - } - - @override - void dispose() { - for (var reactiveService in _reactiveServices) { - reactiveService.removeListener(_indicateChange); - } - super.dispose(); - } - - void _indicateChange() { - notifyListeners(); - } -} - -@protected -class DynamicSourceViewModel extends ReactiveViewModel { - bool changeSource = false; - void notifySourceChanged({bool clearOldData = false}) { - changeSource = true; - } - - @override - List get reactiveServices => []; -} - -class _SingleDataSourceViewModel extends DynamicSourceViewModel { - T? _data; - T? get data => _data; - - dynamic _error; - - @override - dynamic error([Object? object]) => _error; - - bool get dataReady => _data != null && !hasError; -} - -class _MultiDataSourceViewModel extends DynamicSourceViewModel { - Map? _dataMap; - Map? get dataMap => _dataMap; - - bool dataReady(String key) => _dataMap![key] != null && (error(key) == null); -} - -/// Provides functionality for a ViewModel that's sole purpose it is to fetch data using a [Future] -abstract class FutureViewModel extends _SingleDataSourceViewModel - implements Initialisable { - /// The future that fetches the data and sets the view to busy - @Deprecated('Use the futureToRun function') - Future? get future => null; - - // TODO: Add timeout functionality - // TODO: Add retry functionality - default 1 - // TODO: Add retry lifecycle hooks to override in the viewmodel - - Future futureToRun(); - - /// Indicates if you want the error caught in futureToRun to be rethrown - bool get rethrowException => false; - - Future initialise() async { - setError(null); - _error = null; - // We set busy manually as well because when notify listeners is called to clear error messages the - // ui is rebuilt and if you expect busy to be true it's not. - setBusy(true); - notifyListeners(); - - _data = await runBusyFuture(futureToRun(), throwException: true) - .catchError((error) { - setError(error); - _error = error; - setBusy(false); - onError(error); - notifyListeners(); - if (rethrowException) { - throw error; - } - - return null; - }); - - if (_data != null) { - onData(_data); - } - - changeSource = false; - } - - /// Called when an error occurs within the future being run - void onError(error) {} - - /// Called after the data has been set - void onData(T? data) {} -} - -/// Provides functionality for a ViewModel to run and fetch data using multiple future -abstract class MultipleFutureViewModel extends _MultiDataSourceViewModel - implements Initialisable { - Map get futuresMap; - - late Completer _futuresCompleter; - late int _futuresCompleted; - - void _initialiseData() { - if (_dataMap == null) { - _dataMap = Map(); - } - - _futuresCompleted = 0; - } - - Future initialise() { - _futuresCompleter = Completer(); - _initialiseData(); - // We set busy manually as well because when notify listeners is called to clear error messages the - // ui is rebuilt and if you expect busy to be true it's not. - setBusy(true); - notifyListeners(); - - for (var key in futuresMap.keys) { - runBusyFuture(futuresMap[key]!(), busyObject: key, throwException: true) - .then((futureData) { - _dataMap![key] = futureData; - setBusyForObject(key, false); - notifyListeners(); - onData(key); - _incrementAndCheckFuturesCompleted(); - }).catchError((error) { - setErrorForObject(key, error); - setBusyForObject(key, false); - onError(key: key, error: error); - notifyListeners(); - _incrementAndCheckFuturesCompleted(); - }); - } - setBusy(false); - changeSource = false; - - return _futuresCompleter.future; - } - - void _incrementAndCheckFuturesCompleted() { - _futuresCompleted++; - if (_futuresCompleted == futuresMap.length && - !_futuresCompleter.isCompleted) { - _futuresCompleter.complete(); - } - } - - void onError({String? key, error}) {} - - void onData(String key) {} -} - -/// Provides functionality for a ViewModel to run and fetch data using multiple streams -abstract class MultipleStreamViewModel extends _MultiDataSourceViewModel - implements Initialisable { - // Every MultipleStreamViewModel must override streamDataMap - // StreamData requires a stream, but lifecycle events are optional - // if a lifecyle event isn't defined we use the default ones here - Map get streamsMap; - - Map? _streamsSubscriptions; - - @visibleForTesting - Map? get streamsSubscriptions => - _streamsSubscriptions; - - /// Returns the stream subscription associated with the key - StreamSubscription? getSubscriptionForKey(String key) => - _streamsSubscriptions![key]; - - void initialise() { - _dataMap = Map(); - clearErrors(); - _streamsSubscriptions = Map(); - - if (!changeSource) { - notifyListeners(); - } - var _streamsMapValues = Map.from(streamsMap); - - for (var key in _streamsMapValues.keys) { - // If a lifecycle function isn't supplied, we fallback to default - _streamsSubscriptions![key] = _streamsMapValues[key]!.stream.listen( - (incomingData) { - setErrorForObject(key, null); - notifyListeners(); - // Extra security in case transformData isnt sent - var interceptedData = _streamsMapValues[key]!.transformData == null - ? transformData(key, incomingData) - : _streamsMapValues[key]!.transformData!(incomingData); - - if (interceptedData != null) { - _dataMap![key] = interceptedData; - } else { - _dataMap![key] = incomingData; - } - - notifyListeners(); - _streamsMapValues[key]!.onData != null - ? _streamsMapValues[key]!.onData!(_dataMap![key]) - : onData(key, _dataMap![key]); - }, - onError: (error) { - setErrorForObject(key, error); - _dataMap![key] = null; - - _streamsMapValues[key]!.onError != null - ? _streamsMapValues[key]!.onError!(error) - : onError(key, error); - notifyListeners(); - }, - ); - _streamsMapValues[key]!.onSubscribed != null - ? _streamsMapValues[key]!.onSubscribed!() - : onSubscribed(key); - changeSource = false; - } - } - - @override - void notifySourceChanged({bool clearOldData = false}) { - changeSource = true; - _disposeAllSubscriptions(); - - if (clearOldData) { - dataMap!.clear(); - clearErrors(); - } - - notifyListeners(); - } - - void onData(String key, dynamic data) {} - void onSubscribed(String key) {} - void onError(String key, error) {} - void onCancel(String key) {} - dynamic transformData(String key, data) { - return data; - } - - @override - @mustCallSuper - void dispose() { - _disposeAllSubscriptions(); - super.dispose(); - } - - void _disposeAllSubscriptions() { - if (_streamsSubscriptions != null) { - for (var key in _streamsSubscriptions!.keys) { - _streamsSubscriptions![key]!.cancel(); - onCancel(key); - } - - _streamsSubscriptions!.clear(); - } - } -} - -abstract class StreamViewModel extends _SingleDataSourceViewModel - implements DynamicSourceViewModel, Initialisable { - /// Stream to listen to - Stream get stream; - - StreamSubscription? get streamSubscription => _streamSubscription; - - StreamSubscription? _streamSubscription; - - @override - void notifySourceChanged({bool clearOldData = false}) { - changeSource = true; - _streamSubscription?.cancel(); - _streamSubscription = null; - - if (clearOldData) { - _data = null; - } - - notifyListeners(); - } - - void initialise() { - _streamSubscription = stream.listen( - (incomingData) { - setError(null); - _error = null; - notifyListeners(); - // Extra security in case transformData isnt sent - var interceptedData = transformData(incomingData); - - if (interceptedData != null) { - _data = interceptedData; - } else { - _data = incomingData; - } - - onData(_data); - notifyListeners(); - }, - onError: (error) { - setError(error); - _error = error; - _data = null; - onError(error); - notifyListeners(); - }, - ); - - onSubscribed(); - changeSource = false; - } - - /// Called before the notifyListeners is called when data has been set - void onData(T? data) {} - - /// Called when the stream is listened too - void onSubscribed() {} - - /// Called when an error is fired in the stream - void onError(error) {} - - void onCancel() {} - - /// Called before the data is set for the ViewModel - T transformData(T data) { - return data; - } - - @override - void dispose() { - _streamSubscription!.cancel(); - onCancel(); - - super.dispose(); - } -} - -class StreamData extends _SingleDataSourceViewModel { - Stream stream; - - /// Called when the new data arrives - /// - /// notifyListeners is called before this so no need to call in here unless you're - /// running additional logic and setting a separate value. - Function? onData; - - /// Called after the stream has been listened too - Function? onSubscribed; - - /// Called when an error is placed on the stream - Function? onError; - - /// Called when the stream is cancelled - Function? onCancel; - - /// Allows you to modify the data before it's set as the new data for the ViewModel - /// - /// This can be used to modify the data if required. If nothhing is returned the data - /// won't be set. - Function? transformData; - StreamData( - this.stream, { - this.onData, - this.onSubscribed, - this.onError, - this.onCancel, - this.transformData, - }); - late StreamSubscription _streamSubscription; - - void initialise() { - _streamSubscription = stream.listen( - (incomingData) { - setError(null); - _error = null; - notifyListeners(); - // Extra security in case transformData isnt sent - var interceptedData = - transformData == null ? incomingData : transformData!(incomingData); - - if (interceptedData != null) { - _data = interceptedData; - } else { - _data = incomingData; - } - - notifyListeners(); - onData!(_data); - }, - onError: (error) { - setError(error); - _data = null; - onError!(error); - notifyListeners(); - }, - ); - - onSubscribed!(); - } - - @override - void dispose() { - _streamSubscription.cancel(); - onCancel!(); - - super.dispose(); - } -} - -/// Interface: Additional actions that should be implemented by spcialised ViewModels -abstract class Initialisable { - void initialise(); -} diff --git a/packages/stacked/lib/src/state_management/index_tracking_viewmodel.dart b/packages/stacked/lib/src/state_management/index_tracking_viewmodel.dart deleted file mode 100644 index 734e968e4..000000000 --- a/packages/stacked/lib/src/state_management/index_tracking_viewmodel.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:stacked/src/state_management/base_view_models.dart'; -import 'package:stacked/src/state_management/reactive_service_mixin.dart'; - -class IndexTrackingViewModel extends ReactiveViewModel { - int _currentIndex = 0; - int get currentIndex => _currentIndex; - - bool _reverse = false; - - /// Indicates whether we're going forward or backward in terms of the index we're changing. - /// This is very helpful for the page transition directions. - bool get reverse => _reverse; - - void setIndex(int value) { - if (value < _currentIndex) { - _reverse = true; - } else { - _reverse = false; - } - _currentIndex = value; - notifyListeners(); - } - - bool isIndexSelected(int index) => _currentIndex == index; - - @override - List get reactiveServices => []; -} diff --git a/packages/stacked/lib/stacked.dart b/packages/stacked/lib/stacked.dart deleted file mode 100644 index f2aa990f3..000000000 --- a/packages/stacked/lib/stacked.dart +++ /dev/null @@ -1,21 +0,0 @@ -library stacked; - -export 'src/state_management/base_view_models.dart'; -export 'src/state_management/index_tracking_viewmodel.dart'; -export 'src/state_management/form_viewmodel.dart'; -export 'src/state_management/reactive_service_mixin.dart'; -export 'src/state_management/view_model_builder.dart'; -export 'src/state_management/view_model_builder_widget.dart'; -export 'src/state_management/view_model_widget.dart'; -export 'src/state_management/skeleton_loader.dart'; - -export 'src/code_generation/router/extended_navigator.dart'; -export 'src/code_generation/router/router_base.dart'; -export 'src/code_generation/router/route_guard.dart'; -export 'src/code_generation/router/route_def.dart'; -export 'src/code_generation/router/router_utils.dart'; -export 'src/code_generation/router/transitions_builders.dart'; -export 'src/code_generation/stacked_locator.dart'; - -export 'src/reactive/reactive_value/reactive_value.dart'; -export 'src/reactive/reactive_list/reactive_list.dart'; diff --git a/packages/stacked/lib/stacked_annotations.dart b/packages/stacked/lib/stacked_annotations.dart deleted file mode 100644 index 32a2c3b2d..000000000 --- a/packages/stacked/lib/stacked_annotations.dart +++ /dev/null @@ -1,6 +0,0 @@ -export 'src/code_generation/stacked_router_annotations.dart'; -export 'src/code_generation/stacked_locator_annotations.dart'; -export 'src/code_generation/stacked_logger_annotations.dart'; -export 'src/code_generation/forms/stacked_form_annotations.dart'; -export 'src/code_generation/stacked_app.dart'; -export 'src/code_generation/environment_filter.dart'; diff --git a/packages/stacked/pubspec.yaml b/packages/stacked/pubspec.yaml deleted file mode 100644 index e85116801..000000000 --- a/packages/stacked/pubspec.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: stacked -description: An architecture and widgets for an MVVM inspired architecture in - Flutter. It provides common functionalities required to build a large - application in a understandable manner. - -version: 2.3.3 -homepage: https://github.com/FilledStacks/stacked - -environment: - sdk: ">=2.12.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - get_it: ^7.1.4 #7.1.3 - meta: ^1.3.0 - provider: ^6.0.0 #5.0.0 - collection: ^1.15.0 - - #universal_io - universal_io: ^2.0.4 - -dev_dependencies: - flutter_test: - sdk: flutter diff --git a/packages/stacked/test/index_tracking_viewmodel_test.dart b/packages/stacked/test/index_tracking_viewmodel_test.dart deleted file mode 100644 index 6ed04e2c3..000000000 --- a/packages/stacked/test/index_tracking_viewmodel_test.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:stacked/src/state_management/index_tracking_viewmodel.dart'; - -void main() { - group('IndexTrackingViewmodelTest -', () { - group('setIndex -', () { - test('When called with 1, should set currentIndex to 1', () { - var viewModel = IndexTrackingViewModel(); - viewModel.setIndex(1); - expect(viewModel.currentIndex, 1); - }); - - test('When called with 1, should notifyListeners about update', () { - var viewModel = IndexTrackingViewModel(); - var called = false; - viewModel.addListener(() { - called = true; - }); - viewModel.setIndex(1); - expect(called, true); - }); - - test( - 'When called with 1 and currentIndex was 0, reverse should return false', - () { - var viewModel = IndexTrackingViewModel(); - viewModel.setIndex(1); - expect(viewModel.reverse, false); - }); - - test( - 'When called with 0 and currentIndex was 1, reverse should return true', - () { - var viewModel = IndexTrackingViewModel(); - viewModel.setIndex(1); - - viewModel.setIndex(0); - expect(viewModel.reverse, true); - }); - - test('When called with 1 isIndexSelected should return false for 0', () { - var model = IndexTrackingViewModel(); - model.setIndex(1); - expect(model.isIndexSelected(0), false); - }); - - test('When called with 1 isIndexSelected should return true for 1', () { - var viewModel = IndexTrackingViewModel(); - viewModel.setIndex(1); - expect(viewModel.isIndexSelected(1), true); - }); - }); - }); -} diff --git a/packages/stacked_crashlytics/.gitignore b/packages/stacked_crashlytics/.gitignore deleted file mode 100644 index 1985397a2..000000000 --- a/packages/stacked_crashlytics/.gitignore +++ /dev/null @@ -1,74 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Flutter.podspec -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/Flutter/flutter_export_environment.sh -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 diff --git a/packages/stacked_crashlytics/.metadata b/packages/stacked_crashlytics/.metadata deleted file mode 100644 index 54f6c7b94..000000000 --- a/packages/stacked_crashlytics/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 1d9032c7e1d867f071f2277eb1673e8f9b0274e3 - channel: stable - -project_type: package diff --git a/packages/stacked_crashlytics/CHANGELOG.md b/packages/stacked_crashlytics/CHANGELOG.md deleted file mode 100644 index a0392c996..000000000 --- a/packages/stacked_crashlytics/CHANGELOG.md +++ /dev/null @@ -1,16 +0,0 @@ -## 0.1.3 - -- Updates firebase_crashlytics: ^2.6.1 -- Updates firebase_core: ^1.14.0 - -## 0.1.2 - -- Removed exceptions from unsupported platforms - -## 0.1.1 - -- Added Log Warning param to CrashlyticsOutput - -## 0.1.0 - -- Initial implementation of Crashlytics Service diff --git a/packages/stacked_crashlytics/LICENSE b/packages/stacked_crashlytics/LICENSE deleted file mode 100644 index f9cdc9109..000000000 --- a/packages/stacked_crashlytics/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 FilledStacks and other contributors - -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: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -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. \ No newline at end of file diff --git a/packages/stacked_crashlytics/README.md b/packages/stacked_crashlytics/README.md deleted file mode 100644 index e8dc7050b..000000000 --- a/packages/stacked_crashlytics/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Stacked Crashlytics Service -A service accompanied with a LogOutput that tracks all warnings as non-fatal crash reports and all errors as fatal crash reports. This makes use of the Firebase Crashlytics service. - -## How To use - -* In the pubspec.yaml file under dependencies add - ```dart - stacked_crashlytics: ^0.1.1 - ``` - -* Register the service on to the locator - * If you want to register the service - ```dart - @StackedApp( - dependencies: [ - ... - LazySingleton(classType: CrashlyticsService), - ... - ), - ], - ) - ``` - * If you are presolving into an async instance, you can use - ```dart - @StackedApp( - dependencies: [ - ... - Presolve( - classType: CrashlyticsService, - presolveUsing: CrashlyticsService.getInstance, - ), - ], - ) - ``` - Note: Don't forget to run - - ``` flutter pub run build_runner build --delete-conflicting-outputs ``` - -* Add the log Output inside your logger under outputs inside multiple logger output like so - ```dart - Logger( - ... - output: MultipleLoggerOutput([ - ... - CrashlyticsOutput(), - ]), - ); - ``` \ No newline at end of file diff --git a/packages/stacked_crashlytics/lib/src/crashlytics_service.dart b/packages/stacked_crashlytics/lib/src/crashlytics_service.dart deleted file mode 100644 index 9bbe3f6fc..000000000 --- a/packages/stacked_crashlytics/lib/src/crashlytics_service.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:firebase_crashlytics/firebase_crashlytics.dart'; -import 'package:flutter/material.dart'; -// ignore: implementation_imports -import 'package:flutter/src/foundation/assertions.dart'; -import 'package:logger/logger.dart'; - -class CrashlyticsService { - static CrashlyticsService? _instance; - - static Future getInstance() async { - if (_instance == null) { - _instance = CrashlyticsService._(FirebaseCrashlytics.instance); - } - - return _instance!; - } - - final FirebaseCrashlytics _crashlyticsService; - CrashlyticsService._( - this._crashlyticsService, - ); - - void recordFlutterErrorToCrashlytics(FlutterErrorDetails details) { - try { - _crashlyticsService.recordFlutterError(details); - } catch (e) { - _catchOrThrow(e); - } - } - - Future setUserIdToCrashlytics({String? id}) async { - try { - if (id != null) await _crashlyticsService.setUserIdentifier(id); - } catch (e) { - _catchOrThrow(e); - } - } - - Future logToCrashlytics( - Level level, - List lines, - StackTrace stacktrace, { - required bool logwarnings, - }) async { - try { - if (level == Level.error || level == Level.wtf) { - await _crashlyticsService.recordError( - lines.join('\n'), - stacktrace, - printDetails: true, - fatal: true, - ); - } - if (level == Level.warning && logwarnings) { - await _crashlyticsService.recordError( - lines.join('\n'), - stacktrace, - printDetails: true, - ); - } - if (level == Level.info || - level == Level.verbose || - level == Level.debug) { - await _crashlyticsService.log(lines.join('\n')); - } - } catch (exception) { - _catchOrThrow(exception); - } - } - - Future setCustomKeysToTrack(String key, dynamic value) async { - try { - await _crashlyticsService.setCustomKey(key, value); - } catch (e) { - _catchOrThrow(e); - } - } - - // Be very careful when you excute this code it will crash the app - // So, be sure to remove it after usage - void crashApp() { - try { - _crashlyticsService.crash(); - } catch (e) { - _catchOrThrow(e); - } - } - - void _catchOrThrow(dynamic exception) { - final _exceptionString = exception.toString(); - final _isPluginConstantsException = _exceptionString - .contains("pluginConstants['isCrashlyticsCollectionEnabled']"); - - if (!_isPluginConstantsException) { - throw exception; - } - } -} - -class CrashlyticsOutput extends LogOutput { - final bool logWarnings; - CrashlyticsOutput({this.logWarnings = false}); - - @override - void output(OutputEvent event) { - try { - CrashlyticsService.getInstance().then((instance) { - return instance.logToCrashlytics( - event.level, - event.lines, - StackTrace.current, - logwarnings: logWarnings, - ); - }); - } catch (e) { - print('CRASHLYTICS FAILED: $e'); - } - } -} diff --git a/packages/stacked_crashlytics/lib/stacked_crashlytics.dart b/packages/stacked_crashlytics/lib/stacked_crashlytics.dart deleted file mode 100644 index b447c667e..000000000 --- a/packages/stacked_crashlytics/lib/stacked_crashlytics.dart +++ /dev/null @@ -1,3 +0,0 @@ -library stacked_crashlytics; - -export 'src/crashlytics_service.dart'; diff --git a/packages/stacked_crashlytics/pubspec.yaml b/packages/stacked_crashlytics/pubspec.yaml deleted file mode 100644 index 93a6e6c94..000000000 --- a/packages/stacked_crashlytics/pubspec.yaml +++ /dev/null @@ -1,57 +0,0 @@ -name: stacked_crashlytics -description: A package that provides a Crashlytics Service as well as a LogOuput. -version: 0.1.3 -homepage: https://github.com/FilledStacks/stacked - -environment: - sdk: ">=2.15.0 <3.0.0" - flutter: ">=1.17.0" - -dependencies: - flutter: - sdk: flutter - - firebase_core: ^1.14.0 - firebase_crashlytics: ^2.6.1 - logger: ^1.0.0 - -dev_dependencies: - flutter_test: - sdk: flutter - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stacked_crashlytics/test/stacked_crashlytics_test.dart b/packages/stacked_crashlytics/test/stacked_crashlytics_test.dart deleted file mode 100644 index cc1c173ee..000000000 --- a/packages/stacked_crashlytics/test/stacked_crashlytics_test.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:firebase_crashlytics/firebase_crashlytics.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:stacked_crashlytics/stacked_crashlytics.dart'; - -void main() { - group('crashApp -', () { - test('when called, it should crash the app', () async { - var service = await CrashlyticsService.getInstance(); - service.crashApp(); - }); - }); -} diff --git a/packages/stacked_firebase_auth/.gitignore b/packages/stacked_firebase_auth/.gitignore deleted file mode 100644 index 1985397a2..000000000 --- a/packages/stacked_firebase_auth/.gitignore +++ /dev/null @@ -1,74 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Flutter.podspec -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/Flutter/flutter_export_environment.sh -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 diff --git a/packages/stacked_firebase_auth/.metadata b/packages/stacked_firebase_auth/.metadata deleted file mode 100644 index 5eb503478..000000000 --- a/packages/stacked_firebase_auth/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 78910062997c3a836feee883712c241a5fd22983 - channel: stable - -project_type: package diff --git a/packages/stacked_firebase_auth/CHANGELOG.md b/packages/stacked_firebase_auth/CHANGELOG.md deleted file mode 100644 index 28c35e058..000000000 --- a/packages/stacked_firebase_auth/CHANGELOG.md +++ /dev/null @@ -1,76 +0,0 @@ -## 0.2.10+2 - -- Updates 'google_sign_in' to 5.2.1 - -## 0.2.10+1 - -- Updates firebase core to 1.10.0 -- Updates fireabse_auth to 3.2.0 - -## 0.2.10 - -- Initailized Firebase Auth Exceptions error code. - -## 0.2.9 - -- Added Firebase Auth Exceptions error code to FirebaseAuthenticationResult - -## 0.2.8 - -- Bump dependencies to latest - -## 0.2.7 - -- Added a method `updateEmail` to update firebase user email address. - -## 0.2.6 - -- Expose authStateChanges from FirebaseAuth - -## 0.2.5 - -- Anonymous Login Added - -## 0.2.4 - -- Added a method `emailExists` to check if email is registered in Firebase Auth - -## 0.2.3 - -- Added a getter for the current logged in Firebase User - -## 0.2.2+1 - -- Deprecates constructor values for `FirebaseAuthenticationService` and prefers parameters through sign in method - -## 0.2.2 - -- Adds the required apple parameters to `signInWithApple` function - -## 0.2.1 - -- Removed `flutter_facebook_auth` dependency -- Removed implementation for Facebook Authentication - -## 0.1.3 - -- Replaced `flutter_facebook_login` with `flutter_facebook_auth` - -## 0.1.2 - -Updates dependencies - -- `firebase_core`: ^1.0.1 -- `firebase_auth`: ^1.0.1 - -## 0.1.1 - -- Adds `nonce` to remove the probability of a replay attack when using `AppleSignIn` - -## 0.1.0+1 - -- Readme formatting update - -## 0.1.0 - -- Adds the implementations for Email, Google, Facebook and Apple diff --git a/packages/stacked_firebase_auth/LICENSE b/packages/stacked_firebase_auth/LICENSE deleted file mode 100644 index caa0ace17..000000000 --- a/packages/stacked_firebase_auth/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Dane Mackier and other contributors - -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: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -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. \ No newline at end of file diff --git a/packages/stacked_firebase_auth/README.md b/packages/stacked_firebase_auth/README.md deleted file mode 100644 index 7037a45c9..000000000 --- a/packages/stacked_firebase_auth/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Firebase Authentication Service - -This package provides a `FirebaseAuthenticationService` that provides the following logins. - -- Email -- Google -- Apple -- Facebook (Removed `flutter_facebook_auth` dependency and its implementation since it was causing a `MissingPluginException` if the app isn't setup for Facebook Auth) - -It wraps the functionality for those three auth providers. Examples to come soon. This package is being published to improve the [BoxtOut tutorial](https://youtube.com/playlist?list=PLdTodMosi-BzqMe7fU9Bin3z14_hqNHRA) for setting up Firebase Auth. So it's barebones for now. The functionality works great, at the moment I just don't have the time to write a full readme tutorial along with the examples. that will come soon. - -## Future Development - -- [ ] Add functionality to set the logger or use a default one -- [ ] Add the codes thrown from the service into the readme -- [ ] Add option to throw exceptions instead of returning a `FirebaseAuthenticationResult` -- [ ] Add example to the package -- [ ] Add a proper readme -- [ ] Add examples into the readme -- [ ] Add mobile authentication option (implementation already exists, it just needs to be moved in here) -- [ ] Add other authentication providers - -### Breaking Change - -Removed `flutter_facebook_auth` dependency and its implementation since it was causing a `MissingPluginException` if the app isn't setup for Facebook Auth diff --git a/packages/stacked_firebase_auth/lib/src/constants.dart b/packages/stacked_firebase_auth/lib/src/constants.dart deleted file mode 100644 index 84f7a93f6..000000000 --- a/packages/stacked_firebase_auth/lib/src/constants.dart +++ /dev/null @@ -1,5 +0,0 @@ -const String StackedFirebaseAuthAppleClientIdMissing = - 'apple-client-id-missing'; - -const String StackedFirebaseAuthAppleRedirectUriMissing = - 'apple-redirect-uri-missing'; diff --git a/packages/stacked_firebase_auth/lib/src/firebase_authentication_service.dart b/packages/stacked_firebase_auth/lib/src/firebase_authentication_service.dart deleted file mode 100644 index 78a33b95a..000000000 --- a/packages/stacked_firebase_auth/lib/src/firebase_authentication_service.dart +++ /dev/null @@ -1,433 +0,0 @@ -import 'dart:convert'; -import 'dart:math'; - -import 'package:crypto/crypto.dart'; -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:google_sign_in/google_sign_in.dart'; -import 'package:logger/logger.dart'; -import 'package:sign_in_with_apple/sign_in_with_apple.dart'; - -import 'constants.dart'; - -/// Wraps the firebase auth functionality into a service -class FirebaseAuthenticationService { - /// An Instance of Logger that can be used to log out what's happening in the service - final Logger? log; - - /// The URI to which the authorization redirects. It must include a domain name, and can’t be an IP address or localhost. - /// - /// Must be configured at https://developer.apple.com/account/resources/identifiers/list/serviceId - final String? _appleRedirectUri; - - /// The developer’s client identifier, as provided by WWDR. - /// - /// This is the Identifier value shown on the detail view of the service after opening it from https://developer.apple.com/account/resources/identifiers/list/serviceId - /// Usually a reverse domain notation like com.example.app.service - final String? _appleClientId; - - final firebaseAuth = FirebaseAuth.instance; - final _googleSignIn = GoogleSignIn(); - - FirebaseAuthenticationService({ - @Deprecated('Pass in the appleRedirectUri through the signInWithApple function') - String? appleRedirectUri, - @Deprecated('Pass in the appleClientId through the signInWithApple function') - String? appleClientId, - this.log, - }) : _appleRedirectUri = appleRedirectUri, - _appleClientId = appleClientId; - - String? _pendingEmail; - AuthCredential? _pendingCredential; - - Future _signInWithCredential( - AuthCredential credential, - ) async { - return firebaseAuth.signInWithCredential(credential); - } - - /// Returns the current logged in Firebase User - User? get currentUser { - return firebaseAuth.currentUser; - } - - /// Returns the latest userToken stored in the Firebase Auth lib - Future? get userToken { - return firebaseAuth.currentUser?.getIdToken(); - } - - /// Returns true when a user has logged in or signed on this device - bool get hasUser { - return firebaseAuth.currentUser != null; - } - - /// Exposes the authStateChanges functionality. - Stream get authStateChanges { - return firebaseAuth.authStateChanges(); - } - - /// Returns `true` when email has a user registered - Future emailExists(String email) async { - try { - final signInMethods = - await firebaseAuth.fetchSignInMethodsForEmail(email); - - return signInMethods.length > 0; - } on FirebaseAuthException catch (e) { - return e.code.toLowerCase() == 'invalid-email'; - } - } - - Future signInWithGoogle() async { - try { - final GoogleSignInAccount? googleSignInAccount = - await _googleSignIn.signIn(); - if (googleSignInAccount == null) { - log?.i('Process is canceled by the user'); - return FirebaseAuthenticationResult.error( - errorMessage: 'Google Sign In has been cancelled by the user'); - } - final GoogleSignInAuthentication googleSignInAuthentication = - await googleSignInAccount.authentication; - - final AuthCredential credential = GoogleAuthProvider.credential( - accessToken: googleSignInAuthentication.accessToken, - idToken: googleSignInAuthentication.idToken, - ); - - final result = await _signInWithCredential(credential); - - // Link the pending credential with the existing account - if (_pendingCredential != null) { - await result.user?.linkWithCredential(_pendingCredential!); - _clearPendingData(); - } - - return FirebaseAuthenticationResult(user: result.user); - } on FirebaseAuthException catch (e) { - log?.e(e); - return await _handleAccountExists(e); - } catch (e) { - log?.e(e); - return FirebaseAuthenticationResult.error(errorMessage: e.toString()); - } - } - - Future isAppleSignInAvailable() async { - return await SignInWithApple.isAvailable(); - } - - Future signInWithApple({ - required String? appleRedirectUri, - required String? appleClientId, - }) async { - try { - if (appleClientId == null) { - throw FirebaseAuthException( - message: - 'If you want to use Apple Sign In you have to provide a appleClientId to the FirebaseAuthenticationService', - code: StackedFirebaseAuthAppleClientIdMissing, - ); - } - - if (appleRedirectUri == null) { - throw FirebaseAuthException( - message: - 'If you want to use Apple Sign In you have to provide a appleRedirectUri to the FirebaseAuthenticationService', - code: StackedFirebaseAuthAppleClientIdMissing, - ); - } - - // To prevent replay attacks with the credential returned from Apple, we - // include a nonce in the credential request. When signing in in with - // Firebase, the nonce in the id token returned by Apple, is expected to - // match the sha256 hash of `rawNonce`. - final rawNonce = generateNonce(); - final nonce = sha256ofString(rawNonce); - - final appleIdCredential = await SignInWithApple.getAppleIDCredential( - scopes: [ - AppleIDAuthorizationScopes.email, - AppleIDAuthorizationScopes.fullName, - ], - webAuthenticationOptions: WebAuthenticationOptions( - clientId: appleClientId, - redirectUri: Uri.parse(appleRedirectUri), - ), - nonce: nonce, - ); - - final oAuthProvider = OAuthProvider('apple.com'); - final credential = oAuthProvider.credential( - idToken: appleIdCredential.identityToken, - accessToken: appleIdCredential.authorizationCode, - rawNonce: rawNonce, - ); - - final result = await _signInWithCredential(credential); - - // Link the pending credential with the existing account - if (_pendingCredential != null) { - await result.user?.linkWithCredential(_pendingCredential!); - _clearPendingData(); - } - - return FirebaseAuthenticationResult(user: result.user); - } on FirebaseAuthException catch (e) { - log?.e(e); - return await _handleAccountExists(e); - } on SignInWithAppleAuthorizationException catch (e) { - return FirebaseAuthenticationResult.error(errorMessage: e.toString()); - } catch (e) { - log?.e(e); - return FirebaseAuthenticationResult.error(errorMessage: e.toString()); - } - } - - /// Anonymous Login - Future loginAnonymously() async { - try { - log?.d('Anonymoys Login'); - final result = await firebaseAuth.signInAnonymously(); - - return FirebaseAuthenticationResult(user: result.user); - } on FirebaseAuthException catch (e) { - log?.e('A firebase exception has occured. $e'); - return FirebaseAuthenticationResult.error( - exceptionCode: e.code.toLowerCase(), - errorMessage: getErrorMessageFromFirebaseException(e)); - } on Exception catch (e) { - log?.e('A general exception has occured. $e'); - return FirebaseAuthenticationResult.error( - errorMessage: - 'We could not log into your account at this time. Please try again.'); - } - } - - Future loginWithEmail({ - required String email, - required String password, - }) async { - try { - log?.d('email:$email'); - final result = await firebaseAuth.signInWithEmailAndPassword( - email: email, - password: password, - ); - - log?.d('Sign in with email result: ${result.credential} ${result.user}'); - - // Link the pending credential with the existing account - if (_pendingCredential != null) { - await result.user?.linkWithCredential(_pendingCredential!); - _clearPendingData(); - } - - return FirebaseAuthenticationResult(user: result.user); - } on FirebaseAuthException catch (e) { - log?.e('A firebase exception has occured. $e'); - return FirebaseAuthenticationResult.error( - exceptionCode: e.code.toLowerCase(), - errorMessage: getErrorMessageFromFirebaseException(e)); - } on Exception catch (e) { - log?.e('A general exception has occured. $e'); - return FirebaseAuthenticationResult.error( - errorMessage: - 'We could not log into your account at this time. Please try again.'); - } - } - - /// Uses `createUserWithEmailAndPassword` to sign up to the Firebase application - Future createAccountWithEmail({ - required String email, - required String password, - }) async { - try { - log?.d('email:$email'); - final result = await firebaseAuth.createUserWithEmailAndPassword( - email: email, - password: password, - ); - - log?.d( - 'Create user with email result: ${result.credential} ${result.user}'); - - return FirebaseAuthenticationResult(user: result.user); - } on FirebaseAuthException catch (e) { - log?.e('A firebase exception has occured. $e'); - return FirebaseAuthenticationResult.error( - exceptionCode: e.code.toLowerCase(), - errorMessage: getErrorMessageFromFirebaseException(e)); - } on Exception catch (e) { - log?.e('A general exception has occured. $e'); - return FirebaseAuthenticationResult.error( - errorMessage: - 'We could not create your account at this time. Please try again.'); - } - } - - Future _handleAccountExists( - FirebaseAuthException e) async { - if (e.code != 'account-exists-with-different-credential') { - return FirebaseAuthenticationResult.error( - exceptionCode: e.code.toLowerCase(), - errorMessage: e.toString(), - ); - } - - // The account already exists with a different credential - _pendingEmail = e.email; - _pendingCredential = e.credential; - - // Fetch a list of what sign-in methods exist for the conflicting user - List userSignInMethods = - await firebaseAuth.fetchSignInMethodsForEmail(_pendingEmail ?? ''); - - // If the user has several sign-in methods, - // the first method in the list will be the "recommended" method to use. - - // Check if the recommended account is email then tell them to sign up with email - if (userSignInMethods.first == 'password') { - return FirebaseAuthenticationResult.error( - errorMessage: - // 'We don’t have the ability to merge social accounts with existing Delivery Dudes accounts. Log in using the same email as this social platform.', - 'To link your Facebook account with your existing account, please sign in with your email address and password.', - ); - } - - if (userSignInMethods.first == 'google.com') { - return FirebaseAuthenticationResult.error( - errorMessage: - 'We could not log into your account but we noticed you have a Google account with the same details. Please try to login with Google.', - ); - } - - if (userSignInMethods.first == 'apple') { - return FirebaseAuthenticationResult.error( - errorMessage: - 'We could not log into your account but we noticed you have a Apple account with the same details. Please try to login with your Apple account instead.', - ); - } - - // This is here to ensure if we ever get into this function we HAVE to give the user feedback on this error. So we use the sign In methods recommended account - // and the throw the user an exception. - return FirebaseAuthenticationResult.error( - errorMessage: - 'We could not log into your account but we noticed you have a ${userSignInMethods.first} account with the same details. Please try to login with that instead.', - ); - } - - /// Sign out of the social accounts that have been used - Future logout() async { - log?.i(''); - - try { - await firebaseAuth.signOut(); - await _googleSignIn.signOut(); - _clearPendingData(); - } catch (e) { - log?.e('Could not sign out of social account. $e'); - } - } - - void _clearPendingData() { - _pendingEmail = null; - _pendingCredential = null; - } - - /// Send reset password link to email - Future sendResetPasswordLink(String email) async { - log?.i('email:$email'); - - try { - await firebaseAuth.sendPasswordResetEmail(email: email); - return true; - } catch (e) { - log?.e('Could not send email with reset password link. $e'); - return false; - } - } - - /// Validate the current [password] of the Firebase User - Future validatePassword(String password) async { - try { - final authCredentials = EmailAuthProvider.credential( - email: firebaseAuth.currentUser?.email ?? '', - password: password, - ); - - final authResult = await firebaseAuth.currentUser - ?.reauthenticateWithCredential(authCredentials); - - return authResult?.user != null; - } catch (e) { - log?.e('Could not validate the user password. $e'); - return FirebaseAuthenticationResult.error( - errorMessage: 'The current password is not valid.'); - } - } - - /// Update the [password] of the Firebase User - Future updatePassword(String password) async { - await firebaseAuth.currentUser?.updatePassword(password); - } - - /// Update the [email] of the Firebase User - Future updateEmail(String email) async { - await firebaseAuth.currentUser?.updateEmail(email); - } - - /// Generates a cryptographically secure random nonce, to be included in a - /// credential request. - String generateNonce([int length = 32]) { - final charset = - '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._'; - final random = Random.secure(); - return List.generate(length, (_) => charset[random.nextInt(charset.length)]) - .join(); - } - - /// Returns the sha256 hash of [input] in hex notation. - String sha256ofString(String input) { - final bytes = utf8.encode(input); - final digest = sha256.convert(bytes); - return digest.toString(); - } -} - -class FirebaseAuthenticationResult { - /// Firebase user - final User? user; - - /// Contains the error message for the request - final String? errorMessage; - final String? exceptionCode; - - FirebaseAuthenticationResult({this.user}) - : errorMessage = null, - exceptionCode = null; - - FirebaseAuthenticationResult.error({this.errorMessage, this.exceptionCode}) - : user = null; - - /// Returns true if the response has an error associated with it - bool get hasError => errorMessage != null && errorMessage!.isNotEmpty; -} - -String getErrorMessageFromFirebaseException(FirebaseAuthException exception) { - switch (exception.code.toLowerCase()) { - case 'email-already-in-use': - return 'An account already exists for the email you\'re trying to use. Login instead.'; - case 'invalid-email': - return 'The email you\'re using is invalid. Please use a valid email.'; - case 'operation-not-allowed': - return 'The authentication is not enabled on Firebase. Please enable the Authentitcation type on Firebase'; - case 'weak-password': - return 'Your password is too weak. Please use a stronger password.'; - case 'wrong-password': - return 'You seemed to have entered the wrong password. Double check it and try again.'; - default: - return exception.message ?? - 'Something went wrong on our side. Please try again'; - } -} diff --git a/packages/stacked_firebase_auth/lib/stacked_firebase_auth.dart b/packages/stacked_firebase_auth/lib/stacked_firebase_auth.dart deleted file mode 100644 index eff540370..000000000 --- a/packages/stacked_firebase_auth/lib/stacked_firebase_auth.dart +++ /dev/null @@ -1,3 +0,0 @@ -library stacked_firebase_auth; - -export 'src/firebase_authentication_service.dart'; diff --git a/packages/stacked_firebase_auth/pubspec.yaml b/packages/stacked_firebase_auth/pubspec.yaml deleted file mode 100644 index 092d7b855..000000000 --- a/packages/stacked_firebase_auth/pubspec.yaml +++ /dev/null @@ -1,64 +0,0 @@ -name: stacked_firebase_auth -description: A service class that provides Firebase Authentication Functionality on a single api -version: 0.2.10+2 -homepage: https://github.com/FilledStacks/stacked - -environment: - sdk: ">=2.12.0 <3.0.0" - - -dependencies: - flutter: - sdk: flutter - - # Firebase - firebase_core: ^1.10.0 - firebase_auth: ^3.2.0 - - # Firebase Authentications - google_sign_in: ^5.2.1 #5.0.5 - sign_in_with_apple: ^3.2.0 - - # logging - logger: ^1.0.0 - - crypto: ^3.0.1 - -dev_dependencies: - flutter_test: - sdk: flutter - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stacked_firebase_auth/test/stacked_firebase_auth_test.dart b/packages/stacked_firebase_auth/test/stacked_firebase_auth_test.dart deleted file mode 100644 index 8b1378917..000000000 --- a/packages/stacked_firebase_auth/test/stacked_firebase_auth_test.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/stacked_generator/.gitignore b/packages/stacked_generator/.gitignore deleted file mode 100644 index 549d79a93..000000000 --- a/packages/stacked_generator/.gitignore +++ /dev/null @@ -1,72 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.packages -.pub-cache/ -.pub/ -build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/stacked_generator/.metadata b/packages/stacked_generator/.metadata deleted file mode 100644 index d115f7a88..000000000 --- a/packages/stacked_generator/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: e4ebcdf6f4facee5779c38a04d91d08dc58ea7a4 - channel: beta - -project_type: package diff --git a/packages/stacked_generator/CHANGELOG.md b/packages/stacked_generator/CHANGELOG.md deleted file mode 100644 index a870487ed..000000000 --- a/packages/stacked_generator/CHANGELOG.md +++ /dev/null @@ -1,171 +0,0 @@ -## 0.6.2 - -- Enables multi logger output only in release mode - -## 0.6.1 - -- Adds type case for extension getters to adhere to - -```yaml -strong-mode: - implicit-casts: false -``` - -linting rule when generating `ValueProperties` for a form - - - -## 0.6.0 - -- Adds per-field validation message -- Bumps stacked to `2.3.0` - -## 0.5.7 - -- Adds functionality to disable generated logger in release mode - -## 0.5.6 - -- Fixed Route generation with generic return types - -## 0.5.5 - -- Reverts update from 0.5.4 lol. (sorry, some confusion with a different bug) - -## 0.5.4 - -- Reverts update from 0.5.3 - -## 0.5.3 - -- Generate correct return type for returning a route to pop - -## 0.5.2 - -- Adds `orElse` handler in the logger `realFirstLine` variable to fix [issue #455](https://github.com/FilledStacks/stacked/issues/455) -- Disposes the focusNodes while calling `disposeForm` -- Update analyzer dependency to `analyzer: ^2.0` (you can upgrade json_serialization to 5.0 without dependency conflicts) - -## 0.5.1 - -- Added ability to pass parameter to factories with `FactoryWithParam` - -## 0.5.0 - -- Fixes nullable type generation issue on `@PathParam` and `@QueryParam` - -## 0.4.9 - -- Fixes Default parameter value generation bug on stacked router - [issue #411](https://github.com/FilledStacks/stacked/issues/411) - -## 0.4.8 - -- Added ability to pass custom logger outputs to MultiLoggerOutput - -## 0.4.7 - -- Use the default filter for the logger - -## 0.4.6 - -- Added functionality to supply custom `locator` and `setupLocator` names. - -## 0.4.5 - -- Fixed code generation issue from last release - -## 0.4.4 - -- Fixes stacked version - -## 0.4.3 - -- Added `Environment` on Dependency Injection - -## 0.4.2 - -- Adds `initialValue` parameter for `FormTextField` to support initial value in the `TextEditingController` -- Fixes `Route TransitionsBuilders` generation - -## 0.4.1 - -- Fixes regular expression bug causing function name not to show up in logs - -## 0.4.0 - -- Adds option to generate a logger with formatting and automatic function name printing -- Bumps stacked to `2.1.0` - -## 0.3.3 - -- Fixed Static dropdown list generation - -## 0.3.2 - -- Fixed unexpected empty string import - -## 0.3.1-nullsafety.3 - -- Generate nulllable formView property getters - -## 0.3.1-nullsafety.2 - -- fixes DatePicker changes bugs - -## 0.3.1-nullsafety.1 - -- Adds DatePicker form field option to the Form Generation functionality - -## 0.3.0-nullsafety.1 - -- Migrates to null safety - -## 0.2.7 - -- Bumps build and source gen - -## 0.2.6 - -- Adds the `resolveUsing` code generation for `Singleton` and `LazySingleton` registrations - -## 0.2.5 - -- Removes dependency on logger - -## 0.2.4 - -- Bumps `analyzer` and `build_runner` versions - -## 0.2.3 - -- Fixes issue #240 - -## 0.2.2 - -- Adds support to register a service `asType` when generating the getIt registrations - -## 0.2.1 - -- fixes the incompaitility with using an analyzer version that requires the withNullability argument on .getDisplayString() - -## 0.2.0 - -- Adds form generation functionality - -## 0.1.3 - -- Updates the analyzer dependency to `">=0.39.2 <0.41.2"` - -## 0.1.2 - -- Updates generator to use `StackedLocator` for `.locator.dart` file - -## 0.1.1 - -- Makes the dependencies optional - -## 0.1.0 - Initial functionality - -- Generate a `StackedRouter` from the routes defined on `StackedApp` -- Generate all get_it registrations from the dependencies defined on `StackedApp` diff --git a/packages/stacked_generator/LICENSE b/packages/stacked_generator/LICENSE deleted file mode 100644 index adec66df0..000000000 --- a/packages/stacked_generator/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Milad Akarie - -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: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -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. \ No newline at end of file diff --git a/packages/stacked_generator/README.md b/packages/stacked_generator/README.md deleted file mode 100644 index 398d4d77b..000000000 --- a/packages/stacked_generator/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Stacked Generator - -This package acts as a generator for the [stacked](https://pub.dev/packages/stacked) v1.9.0 and onwards. It aims at generating the routing and the dependencies by defining them on the new `StackedApp` annotation. To read more about the usage of the generated code take a look at the [application setup](https://pub.dev/packages/stacked#application-setup) section of the stacked readme. diff --git a/packages/stacked_generator/build.yaml b/packages/stacked_generator/build.yaml deleted file mode 100644 index fc3c465cc..000000000 --- a/packages/stacked_generator/build.yaml +++ /dev/null @@ -1,32 +0,0 @@ -builders: - # Generate the router file and routes - stackedRouterGenerator: - import: "package:stacked_generator/builder.dart" - builder_factories: ["stackedRouterGenerator"] - build_extensions: { ".dart": [".router.dart"] } - auto_apply: dependents - build_to: source - - # Generator the stacked locator service registrations - stackedLocatorGenerator: - import: "package:stacked_generator/builder.dart" - builder_factories: ["stackedLocatorGenerator"] - build_extensions: { ".dart": [".locator.dart"] } - auto_apply: dependents - build_to: source - - # Generator the form view - stackedFormGenerator: - import: "package:stacked_generator/builder.dart" - builder_factories: ["stackedFormGenerator"] - build_extensions: { ".dart": [".form.dart"] } - auto_apply: dependents - build_to: source - - # Generator the logger - stackedLoggerGenerator: - import: "package:stacked_generator/builder.dart" - builder_factories: ["stackedLoggerGenerator"] - build_extensions: { ".dart": [".logger.dart"] } - auto_apply: dependents - build_to: source diff --git a/packages/stacked_generator/lib/builder.dart b/packages/stacked_generator/lib/builder.dart deleted file mode 100644 index b4c874dc2..000000000 --- a/packages/stacked_generator/lib/builder.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:build/build.dart'; -import 'package:source_gen/source_gen.dart'; -import 'package:stacked_generator/src/generators/forms/stacked_form_generator.dart'; -import 'package:stacked_generator/src/generators/getit/stacked_locator_generator.dart'; -import 'package:stacked_generator/src/generators/logging/stacked_logger_generator.dart'; -import 'package:stacked_generator/src/generators/router/stacked_router_generator.dart'; - -Builder stackedRouterGenerator(BuilderOptions options) { - return LibraryBuilder( - StackedRouterGenerator(), - generatedExtension: '.router.dart', - ); -} - -Builder stackedLocatorGenerator(BuilderOptions options) { - return LibraryBuilder( - StackedLocatorGenerator(), - generatedExtension: '.locator.dart', - ); -} - -Builder stackedFormGenerator(BuilderOptions options) { - return LibraryBuilder( - StackedFormGenerator(), - generatedExtension: '.form.dart', - ); -} - -Builder stackedLoggerGenerator(BuilderOptions options) { - return LibraryBuilder( - StackedLoggerGenerator(), - generatedExtension: '.logger.dart', - ); -} diff --git a/packages/stacked_generator/lib/import_resolver.dart b/packages/stacked_generator/lib/import_resolver.dart deleted file mode 100644 index 5a31677ee..000000000 --- a/packages/stacked_generator/lib/import_resolver.dart +++ /dev/null @@ -1,59 +0,0 @@ -import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/dart/element/type.dart'; -import 'package:path/path.dart' as p; - -class ImportResolver { - final List libs; - final String targetFilePath; - - ImportResolver(this.libs, this.targetFilePath); - - String? resolve(Element? element) { - // return early if source is null or element is a core type - if (element?.source == null || _isCoreDartType(element)) { - return null; - } - - for (var lib in libs) { - if (!_isCoreDartType(lib) && - lib.exportNamespace.definedNames.keys.contains(element?.name)) { - var package = lib.source.uri.pathSegments.first; - if (targetFilePath.startsWith(new RegExp('^$package\/'))) { - return p.posix - .relative(lib.source.uri.path, from: targetFilePath) - .replaceFirst('../', ''); - } else { - return lib.source.uri.toString(); - } - } - } - } - - bool _isCoreDartType(Element? element) { - return element?.source?.fullName == 'dart:core'; - } - - Set resolveAll(DartType type) { - final imports = {}; - final resolvedValue = resolve(type.element); - if (resolvedValue != null) { - imports.add(resolvedValue); - } - imports.addAll(_checkForParameterizedTypes(type)); - return imports..removeWhere((element) => element == ''); - } - - Set _checkForParameterizedTypes(DartType typeToCheck) { - final imports = {}; - if (typeToCheck is ParameterizedType) { - for (DartType type in typeToCheck.typeArguments) { - final resolvedValue = resolve(type.element); - if (resolvedValue != null) { - imports.add(resolvedValue); - } - imports.addAll(_checkForParameterizedTypes(type)); - } - } - return imports; - } -} diff --git a/packages/stacked_generator/lib/route_config_resolver.dart b/packages/stacked_generator/lib/route_config_resolver.dart deleted file mode 100644 index 82944b457..000000000 --- a/packages/stacked_generator/lib/route_config_resolver.dart +++ /dev/null @@ -1,6 +0,0 @@ -export 'src/generators/router/custom_transition_builder.dart'; -export 'src/generators/router/route_config.dart'; -export 'src/generators/router/route_config_resolver.dart'; -export 'src/generators/router/route_guard_config.dart'; -export 'src/generators/router/route_parameter_config.dart'; -export 'src/generators/router/router_config_resolver.dart'; diff --git a/packages/stacked_generator/lib/src/generators/base_generator.dart b/packages/stacked_generator/lib/src/generators/base_generator.dart deleted file mode 100644 index af2078315..000000000 --- a/packages/stacked_generator/lib/src/generators/base_generator.dart +++ /dev/null @@ -1,19 +0,0 @@ -/// Provides functionality common to all Generators -class BaseGenerator { - final StringBuffer stringBuffer = StringBuffer(); - - /// Writes the object supplied next to what was written before without adding a new line - void write(Object obj) => stringBuffer.write(obj); - - /// Writes a line of text into the current [StringBuffer] and adds a space new line after it - void writeLine([Object obj = '']) => stringBuffer.writeln(obj); - - /// Adds a new line after what was written before this - void newLine() => stringBuffer.writeln(); - - /// Sorts the imports and then writed it to the current [StringBuffer] - void sortAndGenerate(Set imports) { - var sorted = imports.toList()..sort(); - sorted.forEach((import) => writeLine("import '$import';")); - } -} diff --git a/packages/stacked_generator/lib/src/generators/enums/dependency_type.dart b/packages/stacked_generator/lib/src/generators/enums/dependency_type.dart deleted file mode 100644 index 84558d75b..000000000 --- a/packages/stacked_generator/lib/src/generators/enums/dependency_type.dart +++ /dev/null @@ -1,7 +0,0 @@ -enum DependencyType { - Factory, - LazySingleton, - Singleton, - PresolvedSingleton, - FactoryWithParam, -} diff --git a/packages/stacked_generator/lib/src/generators/forms/field_config.dart b/packages/stacked_generator/lib/src/generators/forms/field_config.dart deleted file mode 100644 index dec4fb371..000000000 --- a/packages/stacked_generator/lib/src/generators/forms/field_config.dart +++ /dev/null @@ -1,52 +0,0 @@ -/// Described a single field to be generated. -/// -/// TODO: Think about different field types like drop down, image selection, -/// date pickers etc. -abstract class FieldConfig { - /// The name of the form field. This will be used to generate the Key mapping - final String name; - - const FieldConfig({required this.name}); -} - -class TextFieldConfig extends FieldConfig { - final String? initialValue; - const TextFieldConfig({ - required String name, - this.initialValue, - }) : super(name: name); -} - -class DateFieldConfig extends FieldConfig { - DateFieldConfig({required String name}) : super(name: name); -} - -class DropdownFieldConfig extends FieldConfig { - final List items; - const DropdownFieldConfig({required String name, required this.items}) - : super(name: name); -} - -class DropdownFieldItem { - final String title; - final String value; - - DropdownFieldItem({required this.title, required this.value}); -} - -extension ListOfFieldConfigs on List { - List get onlyTextFieldConfigs => this - .where((fieldConfig) => fieldConfig is TextFieldConfig) - .map((t) => t as TextFieldConfig) - .toList(); - - List get onlyDateFieldConfigs => this - .where((fieldConfig) => fieldConfig is DateFieldConfig) - .map((t) => t as DateFieldConfig) - .toList(); - - List get onlyDropdownFieldConfigs => this - .where((fieldConfig) => fieldConfig is DropdownFieldConfig) - .map((t) => t as DropdownFieldConfig) - .toList(); -} diff --git a/packages/stacked_generator/lib/src/generators/forms/form_view_config.dart b/packages/stacked_generator/lib/src/generators/forms/form_view_config.dart deleted file mode 100644 index 149116a8d..000000000 --- a/packages/stacked_generator/lib/src/generators/forms/form_view_config.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'field_config.dart'; - -/// Described the configuration to use for generating the forms -class FormViewConfig { - /// The name of the view that has the [FormView] annotation on it - final String viewName; - - /// Contains a list of configs to use when generating the form data - final List fields; - - FormViewConfig({required this.viewName, required this.fields}); -} diff --git a/packages/stacked_generator/lib/src/generators/forms/stacked_form_content_generator.dart b/packages/stacked_generator/lib/src/generators/forms/stacked_form_content_generator.dart deleted file mode 100644 index 7f76e0706..000000000 --- a/packages/stacked_generator/lib/src/generators/forms/stacked_form_content_generator.dart +++ /dev/null @@ -1,262 +0,0 @@ -import 'package:recase/recase.dart'; -import 'package:stacked_generator/src/generators/base_generator.dart'; -import 'package:stacked_generator/src/generators/forms/field_config.dart'; -import 'package:stacked_generator/src/generators/forms/form_view_config.dart'; - -/// A generator that creates the form code based on the configs passed in -class StackedFormContentGenerator extends BaseGenerator { - final FormViewConfig _formViewConfig; - - StackedFormContentGenerator(this._formViewConfig); - - String generate() { - writeLine("// ignore_for_file: public_member_api_docs"); - - final fields = _formViewConfig.fields; - - // TODO: All of these functions can be moved into a separate util class or service - // and can then be unit tested to avoid simple mistakes. BUT, that's for later. - _generateImports(); - _generateValueMapKeys(fields); - _generateDropdownItemsMap(fields.onlyDropdownFieldConfigs); - _generateFormMixin(); - _generateFormViewModelExtensions(fields); - - return stringBuffer.toString(); - } - - void _generateValueMapKeys(List fields) { - newLine(); - for (var field in fields) { - final caseName = ReCase(field.name); - writeLine( - "const String ${_getFormKeyName(caseName)} = '${caseName.camelCase}';"); - } - newLine(); - } - - void _generateDropdownItemsMap(List fields) { - newLine(); - for (final field in fields) { - final caseName = ReCase(field.name); - writeLine( - "const Map ${caseName.pascalCase}ValueToTitleMap = {", - ); - for (final item in field.items) { - writeLine("'${item.value}': '${item.title}',"); - } - if (field.items.isNotEmpty) writeLine('};'); - } - // if (fields.isNotEmpty) writeLine('};'); - newLine(); - } - - void _generateImports() { - // write route imports - final imports = { - "package:flutter/material.dart", - "package:stacked/stacked.dart" - }; - - var validImports = imports.toSet(); - var dartImports = - validImports.where((element) => element.startsWith('dart')).toSet(); - sortAndGenerate(dartImports); - newLine(); - - var packageImports = - validImports.where((element) => element.startsWith('package')).toSet(); - sortAndGenerate(packageImports); - newLine(); - - var rest = validImports.difference({...dartImports, ...packageImports}); - sortAndGenerate(rest); - newLine(); - } - - void _generateFormMixin() { - final fields = _formViewConfig.fields; - final formName = _formViewConfig.viewName; - writeLine("mixin \$$formName on StatelessWidget {"); - - _generateTextEdittingControllersForTextFields(fields.onlyTextFieldConfigs); - _generateFocusNodesForTextFields(fields.onlyTextFieldConfigs); - _generateListenerRegistrationsForTextFields(fields.onlyTextFieldConfigs); - _generateFormDataUpdateFunctionTorTextControllers( - fields.onlyTextFieldConfigs); - _generateDisposeForTextControllers(fields.onlyTextFieldConfigs); - - writeLine('}'); - } - - void _generateTextEdittingControllersForTextFields( - List fields) { - for (final field in fields) { - writeLine( - 'final TextEditingController ${_getControllerName(field)} = TextEditingController(${_getControllerInitialValue(field)});'); - } - } - - void _generateFocusNodesForTextFields(List fields) { - for (final field in fields) { - writeLine('final FocusNode ${field.name}FocusNode = FocusNode();'); - } - } - - void _generateListenerRegistrationsForTextFields( - List fields) { - writeLine(''' - /// Registers a listener on every generated controller that calls [model.setData()] - /// with the latest textController values - void listenToFormUpdated(FormViewModel model) { - '''); - - for (final field in fields) { - writeLine( - '${_getControllerName(field)}.addListener(() => _updateFormData(model));'); - } - writeLine('}'); - newLine(); - } - - void _generateFormDataUpdateFunctionTorTextControllers( - List fields) { - writeLine(''' - /// Updates the formData on the FormViewModel - void _updateFormData(FormViewModel model) => model.setData( - model.formValueMap - ..addAll({ - '''); - - for (final field in fields) { - final caseName = ReCase(field.name); - writeLine( - '${_getFormKeyName(caseName)}: ${_getControllerName(field)}.text,'); - } - - writeLine(''' - }), - ); - '''); - } - - void _generateDisposeForTextControllers(List fields) { - newLine(); - writeLine(''' - /// Calls dispose on all the generated controllers and focus nodes - void disposeForm() { - // The dispose function for a TextEditingController sets all listeners to null - '''); - - for (final field in fields) { - writeLine('${_getControllerName(field)}.dispose();'); - writeLine('${_getFocusNodeName(field)}.dispose();'); - } - - writeLine('}'); - } - - void _generateFormViewModelExtensions(List fields) { - _generateFormViewModelExtensionForGetters(fields); - _generateFormViewModelExtensionForMethods(fields); - } - - void _generateFormViewModelExtensionForGetters(List fields) { - newLine(); - writeLine('extension ValueProperties on FormViewModel {'); - for (final field in fields) { - final caseName = ReCase(field.name); - final type = _getFormFieldValueType(field); - writeLine( - '$type? get ${caseName.camelCase}Value => this.formValueMap[${_getFormKeyName(caseName)}] as $type?;'); - } - - // Generate the getters that check whether a field is set or not - newLine(); - for (final field in fields) { - final caseName = ReCase(field.name); - writeLine( - 'bool get has${caseName.pascalCase} => this.formValueMap.containsKey(${_getFormKeyName(caseName)});'); - } - - // Generate the getters that check whether a field has validation message or not - newLine(); - for (final field in fields) { - final caseName = ReCase(field.name); - writeLine( - 'bool get has${caseName.pascalCase}ValidationMessage => this.fieldsValidationMessages[${_getFormKeyName(caseName)}]?.isNotEmpty ?? false;'); - } - - // Generate the getters that retrieve validation message - newLine(); - for (final field in fields) { - final caseName = ReCase(field.name); - writeLine( - 'String? get ${caseName.camelCase}ValidationMessage => this.fieldsValidationMessages[${_getFormKeyName(caseName)}];'); - } - - writeLine('}'); - } - - void _generateFormViewModelExtensionForMethods(List fields) { - newLine(); - writeLine('extension Methods on FormViewModel {'); - - // Generate the date pickers - for (final field in fields.onlyDateFieldConfigs) { - final caseName = ReCase(field.name); - writeLine(''' - Future select${caseName.pascalCase}( - {required BuildContext context, - required DateTime initialDate, - required DateTime firstDate, - required DateTime lastDate}) async { - final selectedDate = await showDatePicker( - context: context, - initialDate: initialDate, - firstDate: firstDate, - lastDate: lastDate); - if (selectedDate != null) { - this.setData( - this.formValueMap..addAll({${_getFormKeyName(caseName)}: selectedDate})); - } - } - '''); - newLine(); - } - - // Generate the drop down selected item setter - for (final field in fields.onlyDropdownFieldConfigs) { - final caseName = ReCase(field.name); - writeLine(''' - void set${caseName.pascalCase}(String ${caseName.camelCase}) { - this.setData(this.formValueMap..addAll({${_getFormKeyName(caseName)}: ${caseName.camelCase}})); - } - '''); - newLine(); - } - - // Generate the setters for all fields validation message - newLine(); - for (final field in fields) { - final caseName = ReCase(field.name); - writeLine( - 'set${caseName.pascalCase}ValidationMessage(String? validationMessage) => this.fieldsValidationMessages[${_getFormKeyName(caseName)}] = validationMessage;'); - } - - writeLine('}'); - } - - String _getFormFieldValueType(FieldConfig field) { - return field is TextFieldConfig || field is DropdownFieldConfig - ? 'String' - : 'DateTime'; - } - - String _getControllerName(FieldConfig field) => '${field.name}Controller'; - String _getFocusNodeName(FieldConfig field) => '${field.name}FocusNode'; - String _getControllerInitialValue(TextFieldConfig field) => - field.initialValue != null ? "text:'${field.initialValue!}'" : ""; - - String _getFormKeyName(ReCase caseName) => '${caseName.pascalCase}ValueKey'; -} diff --git a/packages/stacked_generator/lib/src/generators/forms/stacked_form_generator.dart b/packages/stacked_generator/lib/src/generators/forms/stacked_form_generator.dart deleted file mode 100644 index dabd1fb59..000000000 --- a/packages/stacked_generator/lib/src/generators/forms/stacked_form_generator.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:analyzer/dart/constant/value.dart'; -import 'package:analyzer/dart/element/element.dart'; -import 'package:build/build.dart'; -import 'package:source_gen/source_gen.dart'; -import 'package:stacked/stacked_annotations.dart'; -import 'package:stacked_generator/import_resolver.dart'; -import 'package:stacked_generator/src/generators/forms/field_config.dart'; -import 'package:stacked_generator/src/generators/forms/form_view_config.dart'; -import 'package:stacked_generator/src/generators/forms/stacked_form_content_generator.dart'; - -class StackedFormGenerator extends GeneratorForAnnotation { - @override - dynamic generateForAnnotatedElement( - Element classForAnnotation, - ConstantReader formView, - BuildStep buildStep, - ) async { - var libs = await buildStep.resolver.libraries.toList(); - var importResolver = - ImportResolver(libs, classForAnnotation.source?.uri.path ?? ''); - - final viewName = classForAnnotation.displayName; - - final fieldsConfig = formView.peek('fields')?.listValue; - List fields = []; - - if (fieldsConfig != null) { - for (final fieldConfig in fieldsConfig) { - final serialisedField = _readFieldConfig( - fieldConfig: fieldConfig, importResolver: importResolver); - - fields.add(serialisedField); - } - } - - final formViewConfig = FormViewConfig( - fields: fields, - viewName: viewName, - ); - - return StackedFormContentGenerator(formViewConfig).generate(); - } -} - -FieldConfig _readFieldConfig({ - required DartObject fieldConfig, - required ImportResolver importResolver, -}) { - var fieldReader = ConstantReader(fieldConfig); - - bool isTextField = - fieldReader.instanceOf(TypeChecker.fromRuntime(FormTextField)); - bool isDateField = - fieldReader.instanceOf(TypeChecker.fromRuntime(FormDateField)); - bool isDropdownField = - fieldReader.instanceOf(TypeChecker.fromRuntime(FormDropdownField)); - - if (isTextField) { - return _readTextFieldConfig( - fieldReader: fieldReader, importResolver: importResolver); - } else if (isDateField) { - return _readDateFieldConfig( - fieldReader: fieldReader, importResolver: importResolver); - } else if (isDropdownField) { - return _readDropdownFieldConfig( - fieldReader: fieldReader, importResolver: importResolver); - } else { - throw ArgumentError('Unknown form field $fieldConfig'); - } -} - -FieldConfig _readTextFieldConfig({ - required ConstantReader fieldReader, - required ImportResolver importResolver, -}) { - final String name = (fieldReader.peek('name')?.stringValue) ?? ''; - final String? initialValue = (fieldReader.peek('initialValue')?.stringValue); - return TextFieldConfig( - name: name, - initialValue: initialValue, - ); -} - -FieldConfig _readDateFieldConfig({ - required ConstantReader fieldReader, - required ImportResolver importResolver, -}) { - final String name = (fieldReader.peek('name')?.stringValue) ?? ''; - return DateFieldConfig( - name: name, - ); -} - -FieldConfig _readDropdownFieldConfig({ - required ConstantReader fieldReader, - required ImportResolver importResolver, -}) { - final String name = (fieldReader.peek('name')?.stringValue) ?? ''; - final List items = - (fieldReader.peek('items')?.listValue.map((dartObject) { - final itemReader = ConstantReader(dartObject); - final title = itemReader.peek('title')?.stringValue ?? ''; - final value = itemReader.peek('value')?.stringValue ?? ''; - - return DropdownFieldItem(title: title, value: value); - }).toList()) ?? - []; - return DropdownFieldConfig( - name: name, - items: items, - ); -} diff --git a/packages/stacked_generator/lib/src/generators/getit/dependency_config.dart b/packages/stacked_generator/lib/src/generators/getit/dependency_config.dart deleted file mode 100644 index bb9ccd385..000000000 --- a/packages/stacked_generator/lib/src/generators/getit/dependency_config.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:stacked_generator/src/generators/enums/dependency_type.dart'; -import 'package:recase/recase.dart'; - -import 'stacked_locator_parameter_resolver.dart'; - -class DependencyConfig { - /// The import to use for the type of the service - final String? import; - - /// The import to use for the abstracted service type - final String? abstractedImport; - - /// The actual name of the class to be registered - final String className; - - /// The abstracted class name of the class to be registered - final String? abstractedTypeClassName; - - /// The type of the service to register - final DependencyType? type; - - /// The static function to use for presolving the service - final String? presolveFunction; - - /// The static function to use for resolving a singleton instance - final String? resolveFunction; - - final Set? environments; - - final Set? params; - - DependencyConfig({ - this.type, - this.params, - this.environments, - required this.import, - required this.className, - this.abstractedImport, - this.abstractedTypeClassName, - this.presolveFunction, - this.resolveFunction, - }); - - /// Returns a camel case version o the [className] - String get camelCaseClassName => ReCase(className).camelCase; -} diff --git a/packages/stacked_generator/lib/src/generators/getit/services_config.dart b/packages/stacked_generator/lib/src/generators/getit/services_config.dart deleted file mode 100644 index f025df0c4..000000000 --- a/packages/stacked_generator/lib/src/generators/getit/services_config.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:stacked_generator/src/generators/getit/dependency_config.dart'; - -/// Describes the configuration of a service to be registered with get_it -class ServicesConfig { - /// A list of services to be registered with the get_it locator - final List services; - - ServicesConfig({required this.services}); -} diff --git a/packages/stacked_generator/lib/src/generators/getit/stacked_locator_content_generator.dart b/packages/stacked_generator/lib/src/generators/getit/stacked_locator_content_generator.dart deleted file mode 100644 index 072cd3290..000000000 --- a/packages/stacked_generator/lib/src/generators/getit/stacked_locator_content_generator.dart +++ /dev/null @@ -1,199 +0,0 @@ -import 'package:stacked_generator/src/generators/base_generator.dart'; -import 'package:stacked_generator/src/generators/enums/dependency_type.dart'; -import 'package:stacked_generator/src/generators/getit/dependency_config.dart'; -import 'package:stacked_generator/src/generators/getit/services_config.dart'; -import 'package:stacked_generator/utils.dart'; - -class StackedLocatorContentGenerator extends BaseGenerator { - final String locatorName; - final String locatorSetupName; - final ServicesConfig servicesConfig; - - StackedLocatorContentGenerator({ - required this.servicesConfig, - required this.locatorName, - required this.locatorSetupName, - }); - - String generate() { - final services = servicesConfig.services; - writeLine("// ignore_for_file: public_member_api_docs"); - - _generateImports(services); - - newLine(); - writeLine('final $locatorName = StackedLocator.instance;'); - newLine(); - - final hasPresolve = services - .any((service) => service.type == DependencyType.PresolvedSingleton); - - writeLine( - '${hasPresolve ? 'Future' : 'void'} $locatorSetupName ({String? environment , EnvironmentFilter? environmentFilter}) ${hasPresolve ? 'async' : ''} {'); - - newLine(); - writeLine('// Register environments'); - - writeLine( - '$locatorName.registerEnvironment(environment: environment, environmentFilter: environmentFilter);'); - - newLine(); - writeLine('// Register dependencies'); - - // Loop through all service definitions and generate the code for it - for (final serviceDefinition in services) { - final registrationCodeForType = _getLocatorRegistrationStringForType( - locatorName: locatorName, - dependencyDefinition: serviceDefinition, - ); - writeLine(registrationCodeForType); - } - - writeLine('}'); - - return stringBuffer.toString(); - } - - String _getLocatorRegistrationStringForType({ - required String locatorName, - required DependencyConfig dependencyDefinition, - }) { - final hasAbstratedType = - dependencyDefinition.abstractedTypeClassName != null; - final abstractionType = hasAbstratedType - ? '<${dependencyDefinition.abstractedTypeClassName}>' - : ''; - - final hasResolveFunction = dependencyDefinition.resolveFunction != null; - final singletonInstanceToReturn = hasResolveFunction - ? '${dependencyDefinition.className}.${dependencyDefinition.resolveFunction}()' - : '${dependencyDefinition.className}()'; - - final String _formattedEnvs = - _getFromatedEnvs(dependencyDefinition.environments ?? {}); - - final _params = _getParameters(dependencyDefinition); - - switch (dependencyDefinition.type) { - case DependencyType.LazySingleton: - return '$locatorName.registerLazySingleton$abstractionType(() => $singletonInstanceToReturn $_formattedEnvs);'; - case DependencyType.PresolvedSingleton: - return ''' - final ${dependencyDefinition.camelCaseClassName} = await ${dependencyDefinition.className}.${dependencyDefinition.presolveFunction}(); - $locatorName.registerSingleton$abstractionType(${dependencyDefinition.camelCaseClassName} $_formattedEnvs); - '''; - case DependencyType.Factory: - return '$locatorName.registerFactory$abstractionType(() => ${dependencyDefinition.className}() $_formattedEnvs);'; - case DependencyType.FactoryWithParam: - throwIf( - _params["params"]?.isEmpty ?? true, - "At least one paramter is requerd for FactoryWithParam registration ", - ); - return '$locatorName.registerFactoryParam<${dependencyDefinition.className},${_params["paramTypes"]}>$abstractionType((param1, param2) => ${dependencyDefinition.className}(${_params["params"]}) $_formattedEnvs);'; - case DependencyType.Singleton: - default: - return '$locatorName.registerSingleton$abstractionType($singletonInstanceToReturn $_formattedEnvs);'; - } - } - - String _getFromatedEnvs(Set envs) { - final _envString = StringBuffer(); - if (envs.isEmpty) { - return _envString.toString(); - } - if (_envString.isEmpty) { - _envString.write(',registerFor:{'); - } - envs.forEach((element) { - if (envs.first == element) { - _envString.write('"$element"'); - } else { - _envString.write(',"$element"'); - } - }); - _envString.write('}'); - return _envString.toString(); - } - - Map _getParameters(DependencyConfig dependencyConfig) { - final factoryParamList = - dependencyConfig.params?.where((element) => element.isFactoryParam); - final int paramLength = factoryParamList?.length ?? 0; - - throwIf( - paramLength > 2, - "Max number of factory params supported by get_it is 2", - ); - - final Set constructorParams = {}; - final List constructorParamTypes = []; - factoryParamList!.toList().asMap().forEach((index, param) { - String getterName; - - throwIf( - !param.type!.contains("?"), - "Factory params must be nullable. Parameter ${param.name} is not nullable", - ); - - getterName = - "param${index + 1}${param.defaultValueCode != null ? ' ?? ${param.defaultValueCode}' : ''}"; - - if (param.isPositional) { - constructorParams.add(getterName); - } else { - constructorParams.add('${param.name}:$getterName'); - } - - constructorParamTypes.add('${param.type}'); - }); - - if (paramLength == 1) { - constructorParamTypes.add('dynamic'); - } - - final params = constructorParams.toString().replaceAll(RegExp('[{}]'), ''); - final paramtypes = - constructorParamTypes.toString().replaceAll(RegExp(r'[\[\]]'), ''); - - return { - "params": params, - "paramTypes": paramtypes, - }; - } - - void _generateImports(List services) { - // write route imports - final imports = { - "package:stacked/stacked.dart", - "package:stacked/stacked_annotations.dart" - }; - - imports.addAll(services.map((service) => service.import)); - imports.addAll(services.map((service) => service.abstractedImport)); - - services.forEach((element) { - if (element.params != null) { - element.params!.forEach((im) { - if (im.imports != null) { - imports.addAll(im.imports!); - } - }); - } - }); - - var validImports = - imports.where((import) => import != null).toSet().cast(); - var dartImports = - validImports.where((element) => element.startsWith('dart')).toSet(); - sortAndGenerate(dartImports); - newLine(); - - var packageImports = - validImports.where((element) => element.startsWith('package')).toSet(); - sortAndGenerate(packageImports); - newLine(); - - var rest = validImports.difference({...dartImports, ...packageImports}); - sortAndGenerate(rest); - } -} diff --git a/packages/stacked_generator/lib/src/generators/getit/stacked_locator_generator.dart b/packages/stacked_generator/lib/src/generators/getit/stacked_locator_generator.dart deleted file mode 100644 index 402eb7eb2..000000000 --- a/packages/stacked_generator/lib/src/generators/getit/stacked_locator_generator.dart +++ /dev/null @@ -1,166 +0,0 @@ -import 'package:analyzer/dart/constant/value.dart'; -import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/dart/element/type.dart'; -import 'package:build/build.dart'; -import 'package:source_gen/source_gen.dart'; - -import 'package:stacked/stacked_annotations.dart'; -import 'package:stacked_generator/import_resolver.dart'; -import 'package:stacked_generator/src/generators/enums/dependency_type.dart'; -import 'package:stacked_generator/src/generators/getit/dependency_config.dart'; -import 'package:stacked_generator/src/generators/getit/stacked_locator_content_generator.dart'; -import 'package:stacked_generator/src/generators/getit/services_config.dart'; -import 'package:stacked_generator/src/generators/getit/stacked_locator_parameter_resolver.dart'; -import 'package:stacked_generator/utils.dart'; - -class StackedLocatorGenerator extends GeneratorForAnnotation { - @override - dynamic generateForAnnotatedElement( - Element element, - ConstantReader stackedApplication, - BuildStep buildStep, - ) async { - var libs = await buildStep.resolver.libraries.toList(); - var importResolver = ImportResolver(libs, element.source?.uri.path ?? ''); - - final String locatorName = - stackedApplication.peek('locatorName')!.stringValue; - - final String locatorSetupName = - stackedApplication.peek('locatorSetupName')!.stringValue; - - throwIf( - locatorName.isEmpty || locatorSetupName.isEmpty, - "Error: locatorName or locatorSetupName can not be a null or empty string", - ); - - final servicesConfig = stackedApplication.peek('dependencies')?.listValue; - if (servicesConfig != null) { - List services = []; - // Convert the services config into Services configuration - for (final serviceConfig in servicesConfig) { - final serialisedServiceConfig = _readDependencyConfig( - dependencyConfig: serviceConfig, - importResolver: importResolver, - ); - services.add(serialisedServiceConfig); - } - - return StackedLocatorContentGenerator( - servicesConfig: ServicesConfig(services: services), - locatorName: toLowerCamelCase(locatorName), - locatorSetupName: toLowerCamelCase(locatorSetupName), - ).generate(); - } - } - - DependencyConfig _readDependencyConfig({ - required DartObject dependencyConfig, - required ImportResolver importResolver, - }) { - var dependencyReader = ConstantReader(dependencyConfig); - // Get the type of the service that we want to register - final DartType? dependencyClassType = - dependencyReader.read('classType').typeValue; - - final DartType? dependencyAbstractedClassType = - dependencyReader.peek('asType')?.typeValue; - - throwIf( - dependencyClassType == null, - 'πŸ›‘ No dependency class Type defined for ${dependencyConfig.toString()}. Please make sure that any of the services provided to the services list in the StackedApp annotation has a service provided. Please see the documentation for stacked_generator if you don\'t know what this means.', - ); - - final classElement = dependencyClassType!.element as ClassElement?; - - throwIf( - classElement == null, - 'πŸ›‘ ${toDisplayString(dependencyClassType)} is not a class element. All services should be classes. We don\'t register individual values for global access through the locator. Make sure the value provided as your service type is a class.', - ); - - final Set? environments = dependencyReader - .peek('environments') - ?.setValue - .map((e) => e.toStringValue()) - .where((element) => element != null) - .toSet() - .cast(); - - // Get the import of the class type that's defined for the service - final import = importResolver.resolve(classElement!); - - final abstractedClassElement = - dependencyAbstractedClassType?.element as ClassElement?; - - final abstractedImport = importResolver.resolve(abstractedClassElement); - - final className = toDisplayString(dependencyClassType); - - final abstractedTypeClassName = dependencyAbstractedClassType != null - ? toDisplayString(dependencyAbstractedClassType) - : null; - - // NOTE: This can be used for actual dependency inject. We do service location instead. - final constructor = classElement.unnamedConstructor; - - final Set clazzParams = {}; - var params = constructor?.parameters; - if (params?.isNotEmpty == true && constructor != null) { - final paramResolver = DependencyParameterResolver(importResolver); - for (ParameterElement p in constructor.parameters) { - clazzParams.add(paramResolver.resolve(p)); - } - } - - final serviceType = _getDependencyType(dependencyReader); - - String? presolveFunction; - if (serviceType == DependencyType.PresolvedSingleton) { - final ConstantReader? presolveUsing = - dependencyReader.peek('presolveUsing'); - final presolveObject = presolveUsing?.objectValue.toFunctionValue(); - presolveFunction = presolveObject?.displayName; - } - - String? resolveFunction; - if (serviceType == DependencyType.LazySingleton || - serviceType == DependencyType.Singleton) { - final ConstantReader? resolveUsing = - dependencyReader.peek('resolveUsing'); - final resolveObject = resolveUsing?.objectValue.toFunctionValue(); - resolveFunction = resolveObject?.displayName; - } - - return DependencyConfig( - className: className, - abstractedTypeClassName: abstractedTypeClassName, - import: import, - abstractedImport: abstractedImport, - type: serviceType, - params: clazzParams, - presolveFunction: presolveFunction, - resolveFunction: resolveFunction, - environments: environments, - ); - } - - DependencyType _getDependencyType(ConstantReader serviceReader) { - if (serviceReader.instanceOf(TypeChecker.fromRuntime(Factory))) { - return DependencyType.Factory; - } - if (serviceReader.instanceOf(TypeChecker.fromRuntime(Singleton))) { - return DependencyType.Singleton; - } - if (serviceReader.instanceOf(TypeChecker.fromRuntime(LazySingleton))) { - return DependencyType.LazySingleton; - } - if (serviceReader.instanceOf(TypeChecker.fromRuntime(Presolve))) { - return DependencyType.PresolvedSingleton; - } - if (serviceReader.instanceOf(TypeChecker.fromRuntime(FactoryWithParam))) { - return DependencyType.FactoryWithParam; - } - - return DependencyType.Singleton; - } -} diff --git a/packages/stacked_generator/lib/src/generators/getit/stacked_locator_parameter_resolver.dart b/packages/stacked_generator/lib/src/generators/getit/stacked_locator_parameter_resolver.dart deleted file mode 100644 index 6bb935734..000000000 --- a/packages/stacked_generator/lib/src/generators/getit/stacked_locator_parameter_resolver.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:analyzer/dart/element/element.dart'; -import 'package:source_gen/source_gen.dart'; -import 'package:stacked/stacked_annotations.dart'; -import 'package:stacked_generator/import_resolver.dart'; -import 'package:stacked_generator/utils.dart'; - -const _factoryParamChecker = TypeChecker.fromRuntime(FactoryParam); - -class DependencyParamConfig { - final String? type; - final String? name; - final String? alias; - final bool isPositional; - final bool isRequired; - final String? defaultValueCode; - final Set? imports; - final bool isFactoryParam; - - DependencyParamConfig({ - this.type, - this.name, - this.alias, - this.isFactoryParam = false, - this.isPositional = false, - this.isRequired = false, - this.defaultValueCode, - this.imports, - }); -} - -class DependencyParameterResolver { - final ImportResolver _importResolver; - final Set imports = {}; - - DependencyParameterResolver(this._importResolver); - - DependencyParamConfig resolve(ParameterElement parameterElement) { - final paramType = parameterElement.type; - - final isFactoryParam = - _factoryParamChecker.hasAnnotationOfExact(parameterElement); - - return DependencyParamConfig( - isFactoryParam: isFactoryParam, - type: toDisplayString(paramType, withNullability: true), - name: parameterElement.name.replaceFirst("_", ''), - isPositional: parameterElement.isPositional, - isRequired: !parameterElement.isOptional, - defaultValueCode: parameterElement.defaultValueCode, - imports: _importResolver.resolveAll(paramType), - ); - } -} diff --git a/packages/stacked_generator/lib/src/generators/logging/logger_class_generator.dart b/packages/stacked_generator/lib/src/generators/logging/logger_class_generator.dart deleted file mode 100644 index a960344ba..000000000 --- a/packages/stacked_generator/lib/src/generators/logging/logger_class_generator.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:stacked_generator/src/generators/base_generator.dart'; -import 'package:stacked_generator/src/generators/logging/logger_config.dart'; - -import 'logger_class_content.dart'; - -/// Generates the app.logger.dart file in the users code base. -class LoggerClassGenerator extends BaseGenerator { - final LoggerConfig _loggerConfig; - - LoggerClassGenerator(LoggerConfig loggerConfig) - : _loggerConfig = loggerConfig; - - Future generate() async { - // TODO: Refactor the way we do this to make more sense. - // TODO: Use O from SOLID principles (open / closed) to Close implementation - final _logHelperNameKey = _loggerConfig.logHelperName; - final _imports = _generateImports(_loggerConfig.imports); - final _multiLogger = _generateMultiLoggers(_loggerConfig.loggerOutputs); - - final _replacedHelperName = - loggerClassContent.replaceFirst(LogHelperNameKey, _logHelperNameKey); - - final _replacedImports = - _replacedHelperName.replaceFirst(MultiLoggerImports, _imports); - - final _replacedConditionalLogger = _replacedImports.replaceFirst( - DisableConsoleOutputInRelease, - _loggerConfig.disableReleaseConsoleOutput ? 'if(!kReleaseMode)' : ''); - - write(_replacedConditionalLogger.replaceFirst( - MultipleLoggerOutput, _multiLogger)); - - return stringBuffer.toString(); - } - - String _generateMultiLoggers(List multiLogger) { - final _multiLoggers = StringBuffer(); - - multiLogger.forEach((element) { - _multiLoggers.write(" if(kReleaseMode) $element(),"); - }); - return _multiLoggers.toString(); - } - - String _generateImports(Set imports) { - final _importBuffer = StringBuffer(); - imports.forEach((element) { - _importBuffer.writeln("import '$element';"); - }); - return _importBuffer.toString(); - } -} diff --git a/packages/stacked_generator/lib/src/generators/logging/logger_config.dart b/packages/stacked_generator/lib/src/generators/logging/logger_config.dart deleted file mode 100644 index 4821463ff..000000000 --- a/packages/stacked_generator/lib/src/generators/logging/logger_config.dart +++ /dev/null @@ -1,20 +0,0 @@ -/// Described the logger functionality to generate in the app -class LoggerConfig { - /// The name of the globally accessible function to return an instance of your Logger - final String logHelperName; - final Set imports; - final List loggerOutputs; - - /// When set to true console logs will not be printed in release mode - /// Default is true - final bool disableReleaseConsoleOutput; - // Future - // final bool enableGoogleCloudLogging; - - LoggerConfig({ - this.imports = const {}, - this.loggerOutputs = const [], - this.logHelperName = 'getLogger', - this.disableReleaseConsoleOutput = true, - }); -} diff --git a/packages/stacked_generator/lib/src/generators/logging/logger_config_resolver.dart b/packages/stacked_generator/lib/src/generators/logging/logger_config_resolver.dart deleted file mode 100644 index b4f6b9581..000000000 --- a/packages/stacked_generator/lib/src/generators/logging/logger_config_resolver.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:analyzer/dart/constant/value.dart'; -import 'package:analyzer/dart/element/element.dart'; -import 'package:source_gen/source_gen.dart'; -import 'package:stacked_generator/import_resolver.dart'; -import 'package:stacked_generator/src/generators/logging/logger_config.dart'; - -/// Reolves the [LoggerConfig] and returns the object if it's supplied -class LoggerConfigResolver { - Future resolve( - ConstantReader stackedApp, ImportResolver importResolver) async { - final _loggerReader = stackedApp.peek('logger'); - final _multiLogger = _loggerReader?.peek('loggerOutputs')?.listValue; - final _logHelperName = - _loggerReader?.peek('logHelperName')?.stringValue ?? 'getLogger'; - - final _disableReleaseConsoleOutput = - _loggerReader?.peek('disableReleaseConsoleOutput')?.boolValue ?? true; - - if (_loggerReader != null) { - return LoggerConfig( - logHelperName: _logHelperName, - imports: _resolveImports( - importResolver: importResolver, - multiLogger: _multiLogger, - ), - loggerOutputs: _resolveMultiLogger(_multiLogger), - disableReleaseConsoleOutput: _disableReleaseConsoleOutput, - ); - } - - return null; - } - - List _resolveMultiLogger(List? multiLogger) { - if (multiLogger != null) { - return multiLogger - .map((e) => e.toTypeValue()?.getDisplayString(withNullability: false)) - .where((element) => element != null) - .toList() - .cast(); - } else { - return []; - } - } - - Set _resolveImports({ - List? multiLogger, - required ImportResolver importResolver, - }) { - if (multiLogger != null) { - return multiLogger - .where((element) => element.toTypeValue() != null) - .map((e) => importResolver.resolve(_dartOjectToElemet(e))) - .toSet() - .cast(); - } else { - return {}; - } - } - - ClassElement _dartOjectToElemet(DartObject obj) { - var dependencyReader = ConstantReader(obj).typeValue; - return dependencyReader.element as ClassElement; - } -} diff --git a/packages/stacked_generator/lib/src/generators/logging/stacked_logger_generator.dart b/packages/stacked_generator/lib/src/generators/logging/stacked_logger_generator.dart deleted file mode 100644 index c5251299e..000000000 --- a/packages/stacked_generator/lib/src/generators/logging/stacked_logger_generator.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'dart:async'; - -import 'package:analyzer/dart/element/element.dart'; -import 'package:stacked/stacked_annotations.dart'; -import 'package:build/build.dart'; -import 'package:source_gen/source_gen.dart'; -import 'package:stacked_generator/import_resolver.dart'; - -import 'logger_class_generator.dart'; -import 'logger_config_resolver.dart'; - -class StackedLoggerGenerator extends GeneratorForAnnotation { - @override - FutureOr generateForAnnotatedElement( - Element element, - ConstantReader annotation, - BuildStep buildStep, - ) async { - var loggerResolver = LoggerConfigResolver(); - var libs = await buildStep.resolver.libraries.toList(); - var importResolver = ImportResolver(libs, element.source?.uri.path ?? ''); - - final loggerConfig = await loggerResolver.resolve( - annotation, - importResolver, - ); - - if (loggerConfig != null) { - return LoggerClassGenerator(loggerConfig).generate(); - } - - return Future.value(''); - } -} diff --git a/packages/stacked_generator/lib/src/generators/router/custom_transition_builder.dart b/packages/stacked_generator/lib/src/generators/router/custom_transition_builder.dart deleted file mode 100644 index 6127eb1e8..000000000 --- a/packages/stacked_generator/lib/src/generators/router/custom_transition_builder.dart +++ /dev/null @@ -1,8 +0,0 @@ -// holds the name and path of the passed -// transition builder function -class CustomTransitionBuilder { - String name; - String? import; - - CustomTransitionBuilder(this.name, this.import); -} diff --git a/packages/stacked_generator/lib/src/generators/router/route_config.dart b/packages/stacked_generator/lib/src/generators/router/route_config.dart deleted file mode 100644 index 4a36500c4..000000000 --- a/packages/stacked_generator/lib/src/generators/router/route_config.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:stacked_generator/route_config_resolver.dart'; - -import 'custom_transition_builder.dart'; - -/// holds the extracted route configs -/// to be used in [RouterClassGenerator] - -class RouteConfig { - List imports = []; - late String name; - late String pathName; - bool? initial; - bool? fullscreenDialog; - bool? customRouteOpaque; - bool? customRouteBarrierDismissible; - bool? maintainState; - String? className; - String? returnType; - List? parameters; - CustomTransitionBuilder? transitionBuilder; - int? durationInMilliseconds; - int? reverseDurationInMilliseconds; - int routeType = RouteType.material; - List guards = []; - String? cupertinoNavTitle; - bool? hasWrapper; - RouterConfig? routerConfig; - - bool? hasConstConstructor; - - String get argumentsHolderClassName { - return '${className}Arguments'; - } - - String? get templateName { - return pathName.contains(":") ? '_$name' : name; - } - - List get argParams { - return parameters - ?.where((p) => !p.isPathParam! && !p.isQueryParam!) - .toList() ?? - []; - } -} - -class RouteType { - static const int material = 0; - static const int cupertino = 1; - static const int adaptive = 2; - static const int custom = 3; -} diff --git a/packages/stacked_generator/lib/src/generators/router/route_config_resolver.dart b/packages/stacked_generator/lib/src/generators/router/route_config_resolver.dart deleted file mode 100644 index aff317576..000000000 --- a/packages/stacked_generator/lib/src/generators/router/route_config_resolver.dart +++ /dev/null @@ -1,151 +0,0 @@ -import 'package:analyzer/dart/element/element.dart'; -import 'package:stacked/stacked_annotations.dart'; -import 'package:stacked_generator/import_resolver.dart'; -import 'package:stacked_generator/route_config_resolver.dart'; -import 'package:stacked_generator/utils.dart'; -import 'package:source_gen/source_gen.dart'; - -const TypeChecker stackedRouteChecker = TypeChecker.fromRuntime(StackedRoute); - -// extracts route configs from class fields -class RouteConfigResolver { - final RouterConfig _routerConfig; - final ImportResolver _importResolver; - - RouteConfigResolver(this._routerConfig, this._importResolver); - - Future resolve(ConstantReader stackedRoute) async { - final routeConfig = RouteConfig(); - final type = stackedRoute.read('page').typeValue; - - final classElement = type.element as ClassElement; - - final import = _importResolver.resolve(classElement); - if (import != null) { - routeConfig.imports.add(import); - } - - routeConfig.className = toDisplayString(type); - var path = stackedRoute.peek('path')?.stringValue; - if (path == null) { - if (stackedRoute.peek('initial')?.boolValue == true) { - path = '/'; - } else { - path = - '${_routerConfig.routeNamePrefix}${toKababCase(routeConfig.className!)}'; - } - } - routeConfig.pathName = path; - - throwIf( - type.element is! ClassElement, - '${toDisplayString(type)} is not a class element', - element: type.element!, - ); - - await _extractRouteMetaData(routeConfig, stackedRoute); - - routeConfig.name = stackedRoute.peek('name')?.stringValue ?? - toLowerCamelCase(routeConfig.className!); - - routeConfig.hasWrapper = classElement.allSupertypes - .map((el) => toDisplayString(el)) - .contains('StackedRouteWrapper'); - - final constructor = classElement.unnamedConstructor; - - var params = constructor?.parameters; - if (params?.isNotEmpty == true) { - if (constructor!.isConst && - params!.length == 1 && - toDisplayString(params.first.type) == 'Key') { - routeConfig.hasConstConstructor = true; - } else { - final paramResolver = RouteParameterResolver(_importResolver); - routeConfig.parameters = []; - for (ParameterElement p in constructor.parameters) { - routeConfig.parameters?.add(await paramResolver.resolve(p)); - } - } - } - // _validatePathParams(routeConfig, classElement); - return routeConfig; - } - - Future _extractRouteMetaData( - RouteConfig routeConfig, ConstantReader stackedRoute) async { - routeConfig.fullscreenDialog = - stackedRoute.peek('fullscreenDialog')?.boolValue; - routeConfig.maintainState = stackedRoute.peek('maintainState')?.boolValue; - - stackedRoute - .peek('guards') - ?.listValue - .map((g) => g.toTypeValue()) - .forEach((guard) { - if (guard != null && guard.element != null) { - routeConfig.guards.add(RouteGuardConfig( - type: toDisplayString(guard), - import: _importResolver.resolve((guard.element)!))); - } - }); - - final returnType = stackedRoute.objectValue.type; - routeConfig.returnType = toDisplayString(returnType!); - - if (routeConfig.returnType != 'dynamic') { - routeConfig.imports.addAll(_importResolver.resolveAll(returnType)); - } - - if (stackedRoute.instanceOf(TypeChecker.fromRuntime(MaterialRoute))) { - routeConfig.routeType = RouteType.material; - } else if (stackedRoute - .instanceOf(TypeChecker.fromRuntime(CupertinoRoute))) { - routeConfig.routeType = RouteType.cupertino; - routeConfig.cupertinoNavTitle = stackedRoute.peek('title')?.stringValue; - } else if (stackedRoute - .instanceOf(TypeChecker.fromRuntime(AdaptiveRoute))) { - routeConfig.routeType = RouteType.adaptive; - routeConfig.cupertinoNavTitle = - stackedRoute.peek('cupertinoPageTitle')?.stringValue; - } else if (stackedRoute.instanceOf(TypeChecker.fromRuntime(CustomRoute))) { - routeConfig.routeType = RouteType.custom; - routeConfig.durationInMilliseconds = - stackedRoute.peek('durationInMilliseconds')?.intValue; - routeConfig.reverseDurationInMilliseconds = - stackedRoute.peek('reverseDurationInMilliseconds')?.intValue; - routeConfig.customRouteOpaque = stackedRoute.peek('opaque')?.boolValue; - routeConfig.customRouteBarrierDismissible = - stackedRoute.peek('barrierDismissible')?.boolValue; - final function = stackedRoute - .peek('transitionsBuilder') - ?.objectValue - .toFunctionValue(); - if (function != null) { - final displayName = function.displayName.replaceFirst(RegExp('^_'), ''); - final functionName = function.isStatic - ? '${function.enclosingElement.displayName}.$displayName' - : displayName; - - var import; - if (function.enclosingElement.name != 'TransitionsBuilders') { - import = _importResolver.resolve(function); - } - routeConfig.transitionBuilder = - CustomTransitionBuilder(functionName, import); - } - } else { - var globConfig = _routerConfig.globalRouteConfig; - routeConfig.routeType = globConfig?.routeType ?? RouteType.material; - if (globConfig?.routeType == RouteType.custom) { - routeConfig.transitionBuilder = globConfig?.transitionBuilder; - routeConfig.durationInMilliseconds = globConfig?.durationInMilliseconds; - routeConfig.reverseDurationInMilliseconds = - globConfig?.reverseDurationInMilliseconds; - routeConfig.customRouteBarrierDismissible = - globConfig?.customRouteBarrierDismissible; - routeConfig.customRouteOpaque = globConfig?.customRouteOpaque; - } - } - } -} diff --git a/packages/stacked_generator/lib/src/generators/router/route_guard_config.dart b/packages/stacked_generator/lib/src/generators/router/route_guard_config.dart deleted file mode 100644 index fd034b581..000000000 --- a/packages/stacked_generator/lib/src/generators/router/route_guard_config.dart +++ /dev/null @@ -1,6 +0,0 @@ -class RouteGuardConfig { - final String? type; - final String? import; - - RouteGuardConfig({this.type, this.import}); -} diff --git a/packages/stacked_generator/lib/src/generators/router/route_parameter_config.dart b/packages/stacked_generator/lib/src/generators/router/route_parameter_config.dart deleted file mode 100644 index 098989389..000000000 --- a/packages/stacked_generator/lib/src/generators/router/route_parameter_config.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:analyzer/dart/element/element.dart'; -import 'package:stacked/stacked_annotations.dart'; -import 'package:stacked_generator/import_resolver.dart'; -import 'package:source_gen/source_gen.dart'; -import 'package:stacked_generator/utils.dart'; - -final pathParamChecker = TypeChecker.fromRuntime(PathParam); -final queryParamChecker = TypeChecker.fromRuntime(QueryParam); - -/// holds constructor parameter info to be used -/// in generating route parameters. -class RouteParamConfig { - final String? type; - final String? name; - final String? alias; - final bool isPositional; - final bool isRequired; - final bool? isPathParam; - final bool? isQueryParam; - final String? defaultValueCode; - final Set? imports; - - RouteParamConfig({ - this.type, - this.name, - this.alias, - this.isPositional = false, - this.isRequired = false, - this.isPathParam, - this.isQueryParam, - this.defaultValueCode, - this.imports, - }); - - String get getterName { - switch (type) { - case 'String': - return 'getString'; - case 'int': - return 'getInt'; - case 'double': - return 'getDouble'; - case 'num': - return 'getNum'; - case 'bool': - return 'getBool'; - case 'String?': - return 'optString'; - case 'int?': - return 'optInt'; - case 'double?': - return 'optDouble'; - case 'num?': - return 'optNum'; - case 'bool?': - return 'optBool'; - default: - return 'value'; - } - } - - String? get paramName => alias ?? name; -} - -class RouteParameterResolver { - final ImportResolver _importResolver; - final Set imports = {}; - - RouteParameterResolver(this._importResolver); - - Future resolve(ParameterElement parameterElement) async { - final paramType = parameterElement.type; - - var pathParam = pathParamChecker.hasAnnotationOfExact(parameterElement); - var paramAlias; - if (pathParam) { - paramAlias = pathParamChecker - .firstAnnotationOf(parameterElement) - ?.getField('name') - ?.toStringValue(); - } - var isQuery = queryParamChecker.hasAnnotationOfExact(parameterElement); - if (isQuery) { - paramAlias = queryParamChecker - .firstAnnotationOf(parameterElement) - ?.getField('name') - ?.toStringValue(); - } - - return RouteParamConfig( - type: toDisplayString(paramType, withNullability: true), - name: parameterElement.name.replaceFirst("_", ''), - alias: paramAlias, - isPositional: parameterElement.isPositional, - isRequired: !parameterElement.isOptional, - isPathParam: pathParam, - isQueryParam: isQuery, - defaultValueCode: parameterElement.defaultValueCode, - imports: _importResolver.resolveAll(paramType)); - } -} diff --git a/packages/stacked_generator/lib/src/generators/router/router_class_generator.dart b/packages/stacked_generator/lib/src/generators/router/router_class_generator.dart deleted file mode 100644 index 6f5ceb352..000000000 --- a/packages/stacked_generator/lib/src/generators/router/router_class_generator.dart +++ /dev/null @@ -1,382 +0,0 @@ -import 'package:stacked_generator/route_config_resolver.dart'; -import 'package:stacked_generator/src/generators/base_generator.dart'; -import 'package:stacked_generator/utils.dart'; - -class RouterClassGenerator extends BaseGenerator { - final RouterConfig _rootRouterConfig; - - RouterClassGenerator(this._rootRouterConfig); - - String generate() { - writeLine("// ignore_for_file: public_member_api_docs"); - var allRouters = _rootRouterConfig.collectAllRoutersIncludingParent; - var allRoutes = allRouters.fold>( - [], (all, e) => all..addAll(e.routes ?? [])).toList(); - _generateImports(allRoutes); - - allRouters.forEach((routerConfig) { - _generateRoutesClass(routerConfig); - _generateRouterClass(routerConfig); - - if (_rootRouterConfig.generateNavigationHelper ?? false) { - _generateNavigationHelpers(routerConfig); - } - }); - - _generateArgumentHolders(allRoutes); - - return stringBuffer.toString(); - } - - void _generateImports(List routes) { - // write route imports - final imports = { - "package:stacked/stacked.dart", - if (routes.any((e) => - e.routeType == RouteType.material || e.routeType == RouteType.custom)) - "package:flutter/material.dart", - if (routes.any((e) => e.routeType == RouteType.cupertino)) - "package:flutter/cupertino.dart", - }; - routes.forEach((route) { - imports.addAll(route.imports); - if (route.transitionBuilder != null) { - if (route.transitionBuilder!.import != null) { - imports.add(route.transitionBuilder!.import); - } - } - if (route.parameters != null) { - route.parameters!.where((p) => p.imports != null).forEach((param) { - imports.addAll(param.imports!); - }); - } - - route.guards.forEach((g) => imports.add(g.import!)); - }); - - var validImports = - imports.where((import) => import != null).toSet().cast(); - var dartImports = - validImports.where((element) => element.startsWith('dart')).toSet(); - sortAndGenerate(dartImports); - newLine(); - - var packageImports = - validImports.where((element) => element.startsWith('package')).toSet(); - sortAndGenerate(packageImports); - newLine(); - - var rest = validImports.difference({...dartImports, ...packageImports}); - sortAndGenerate(rest); - } - - void _generateRoutesClass(RouterConfig routerConfig) { - writeLine('class ${routerConfig.routesClassName} {'); - var allNames = {}; - routerConfig.routes?.forEach((r) { - final routeName = r.name; - final path = r.pathName; - - if (path.contains(':')) { - // handle template paths - writeLine("static const String _$routeName = '$path';"); - allNames.add('_$routeName'); - var params = RegExp(r':([^/]+)').allMatches(path).map((m) { - var match = m.group(1); - if (match!.endsWith('?')) { - return "dynamic ${match.substring(0, match.length - 1)} = ''"; - } else { - return "@required dynamic $match"; - } - }); - writeLine( - "static String $routeName({${params.join(',')}}) => '${path.replaceAllMapped(RegExp(r'([:])|([?])'), (m) { - if (m[1] != null) { - return '\$'; - } else { - return ''; - } - })}';", - ); - } else { - allNames.add(routeName); - writeLine("static const String $routeName = '$path';"); - } - }); - writeLine("static const all = {"); - allNames.forEach((name) => write('$name,')); - write("};"); - writeLine('}'); - } - - void _generateRouteTemplates(RouterConfig routerConfig) { - newLine(); - routerConfig.routes?.forEach((r) { - writeLine("RouteDef(${routerConfig.routesClassName}.${r.templateName}"); - writeLine(",page: ${r.className}"); - if (r.guards.isNotEmpty == true) { - writeLine(",guards:${r.guards.map((e) => e.type).toList().toString()}"); - } - if (r.routerConfig != null) { - writeLine(",generator: ${r.routerConfig!.routerClassName}(),"); - } - writeLine('),'); - }); - } - - void _generateRouteGeneratorFunction(RouterConfig routerConfig) { - newLine(); - - var routesMap = {}; - routerConfig.routes?.forEach((route) { - routesMap[route.className!] = route; - }); - - routesMap.forEach((name, route) { - writeLine('$name: (data) {'); - _generateRoute(route); - //close builder - write("},"); - }); - } - - void _generateRoute(RouteConfig r) { - List? constructorParams = []; - - if (r.parameters?.isNotEmpty == true) { - // if router has any required or positional params the argument class holder becomes required. - final nullOk = !r.argParams.any((p) => p.isRequired || p.isPositional); - // show an error page if passed args are not the same as declared args - - if (r.argParams.isNotEmpty) { - final argsType = r.argumentsHolderClassName; - writeLine('var args = data.getArgs<$argsType>('); - if (!nullOk) { - write('nullOk: false'); - } else { - write("orElse: ()=> $argsType(),"); - } - write(");"); - } - constructorParams = r.parameters?.map((param) { - String getterName; - if (param.isPathParam ?? false) { - getterName = - "data.pathParams['${param.paramName}'].${param.getterName}(${param.defaultValueCode != null ? '${param.defaultValueCode}' : ''})"; - } else if (param.isQueryParam ?? false) { - getterName = - "data.queryParams['${param.paramName}'].${param.getterName}(${param.defaultValueCode != null ? '${param.defaultValueCode}' : ''})"; - } else { - getterName = "args.${param.name}"; - } - if (param.isPositional) { - return getterName; - } else { - return '${param.name}:$getterName'; - } - }).toList(); - } - - // add any empty item to add a comma at end - // when join(',') is called - if (constructorParams!.length > 1) { - constructorParams.add(''); - } - final constructor = - "${r.hasConstConstructor == true ? 'const' : ''} ${r.className}(${constructorParams.join(',')})${(r.hasWrapper ?? false) ? ".wrappedRoute(context)" : ""}"; - - _generateRouteBuilder(r, constructor); - } - - void _generateArgumentHolders(List routes) { - final routesWithArgsHolders = Map(); - - // make sure we only generated holder classes for - // routes with parameters - // also prevent duplicate class with the same name from being generated; - - routes - .where((r) => r.argParams.isNotEmpty) - .forEach((r) => routesWithArgsHolders[r.className!] = r); - - if (routesWithArgsHolders.isNotEmpty) { - _generateBoxed('Arguments holder classes'); - routesWithArgsHolders.values.forEach(_generateArgsHolder); - } - } - - void _generateArgsHolder(RouteConfig routeConfig) { - writeLine('/// ${routeConfig.className} arguments holder class'); - final argsClassName = '${routeConfig.argumentsHolderClassName}'; - - // generate fields - writeLine('class $argsClassName{'); - final params = routeConfig.argParams; - params.forEach((param) { - writeLine('final ${param.type} ${param.name};'); - }); - - // generate constructor - writeLine('$argsClassName({'); - params.asMap().forEach((i, param) { - if (param.isRequired || param.isPositional) { - write('required '); - } - - write('this.${param.name}'); - if (param.defaultValueCode != null) { - write(' = ${param.defaultValueCode}'); - } - if (i != params.length - 1) { - write(','); - } - }); - writeLine('});'); - writeLine('}'); - } - - void _generateBoxed(String message) { - writeLine('\n/// '.padRight(77, '*')); - writeLine('/// $message'); - writeLine('/// '.padRight(77, '*')); - newLine(); - } - - void _generateRouterClass(RouterConfig routerConfig) { - writeLine('\nclass ${routerConfig.routerClassName} extends RouterBase {'); - - writeLine(''' - @override - List get routes => _routes; - final _routes = [ - '''); - _generateRouteTemplates(routerConfig); - write('];'); - - writeLine(''' - @override - Map get pagesMap => _pagesMap; - final _pagesMap = { - '''); - _generateRouteGeneratorFunction(routerConfig); - write('};'); - - // close router class - writeLine('}'); - } - - void _generateRouteBuilder(RouteConfig r, String constructor) { - if (r.returnType != null && r.returnType!.contains('<')) { - if (r.returnType!.contains('CustomRoute') || - r.returnType!.contains('MaterialRoute') || - r.returnType!.contains('CupertinoRoute') || - r.returnType!.contains('AdaptiveRoute')) { - // get the correct return type - final type = r.returnType!.substring( - r.returnType!.indexOf('<') + 1, r.returnType!.lastIndexOf('>')); - r.returnType = type; - } - } - final returnType = r.returnType ?? 'dynamic'; - if (r.routeType == RouteType.cupertino) { - write( - 'return CupertinoPageRoute<$returnType>(builder: (context) => $constructor, settings: data,'); - if (r.cupertinoNavTitle != null) { - write("title:'${r.cupertinoNavTitle}',"); - } - } else if (r.routeType == RouteType.material) { - write( - 'return MaterialPageRoute<$returnType>(builder: (context) => $constructor, settings: data,'); - } else if (r.routeType == RouteType.adaptive) { - write( - 'return buildAdaptivePageRoute<$returnType>(builder: (context) => $constructor, settings: data,'); - if (r.cupertinoNavTitle != null) { - write("cupertinoTitle:'${r.cupertinoNavTitle}',"); - } - } else { - write( - 'return PageRouteBuilder<$returnType>(pageBuilder: (context, animation, secondaryAnimation) => $constructor, settings: data,'); - - if (r.customRouteOpaque != null) { - write('opaque:${r.customRouteOpaque.toString()},'); - } - if (r.customRouteBarrierDismissible != null) { - write( - 'barrierDismissible:${r.customRouteBarrierDismissible.toString()},'); - } - if (r.transitionBuilder != null) { - write('transitionsBuilder: ${r.transitionBuilder!.name},'); - } - if (r.durationInMilliseconds != null) { - write( - 'transitionDuration: const Duration(milliseconds: ${r.durationInMilliseconds}),'); - } - if (r.reverseDurationInMilliseconds != null) { - write( - 'reverseTransitionDuration: const Duration(milliseconds: ${r.reverseDurationInMilliseconds}),'); - } - } - // generated shared props - if (r.fullscreenDialog != null) { - write('fullscreenDialog:${r.fullscreenDialog.toString()},'); - } - if (r.maintainState != null) { - write('maintainState:${r.maintainState.toString()},'); - } - - writeLine(');'); - } - - void _generateNavigationHelpers(RouterConfig routerConfig) { - _generateBoxed('Navigation helper methods extension'); - writeLine( - 'extension ${routerConfig.routerClassName}ExtendedNavigatorStateX on ExtendedNavigatorState {'); - for (var route in routerConfig.routes ?? []) { - // skip routes that has path params until - // until there's a practical way to handle them - if (RegExp(r':([^/]+)').hasMatch(route.pathName)) { - continue; - } - _generateHelperMethod(route, routerConfig.routesClassName!); - } - writeLine('}'); - } - - void _generateHelperMethod(RouteConfig route, String routesClassName) { - final genericType = route.returnType == null ? '' : '<${route.returnType}>'; - write('Future$genericType push${capitalize(route.name)}('); - // generate constructor - if (route.parameters != null) { - write('{'); - route.parameters?.forEach((param) { - if (param.isRequired || param.isPositional) { - write('@required '); - } - - write('${param.type} ${param.name}'); - if (param.defaultValueCode != null) { - write(' = ${param.defaultValueCode}'); - } - write(','); - }); - if (route.guards.isNotEmpty == true) { - write('OnNavigationRejected onReject'); - } - write('}'); - } - writeLine(')'); - write(' => push$genericType($routesClassName.${route.name}'); - if (route.parameters != null) { - write(',arguments: '); - write('${route.argumentsHolderClassName}('); - write( - route.parameters?.map((p) => '${p.name}: ${p.name}').join(',') ?? ''); - write('),'); - - if (route.guards.isNotEmpty == true) { - write('onReject:onReject,'); - } - } - writeLine(');\n'); - } -} diff --git a/packages/stacked_generator/lib/src/generators/router/router_config_resolver.dart b/packages/stacked_generator/lib/src/generators/router/router_config_resolver.dart deleted file mode 100644 index d9ba35157..000000000 --- a/packages/stacked_generator/lib/src/generators/router/router_config_resolver.dart +++ /dev/null @@ -1,146 +0,0 @@ -import 'package:analyzer/dart/constant/value.dart'; -import 'package:stacked/stacked_annotations.dart'; -import 'package:stacked_generator/import_resolver.dart'; -import 'package:stacked_generator/route_config_resolver.dart'; -import 'package:stacked_generator/utils.dart'; -import 'package:source_gen/source_gen.dart'; - -/// Extracts and holds router configs -/// to be used in [RouterClassGenerator] - -class RouterConfig { - final bool? generateNavigationHelper; - final List? routes; - final RouteConfig? globalRouteConfig; - final String? routesClassName; - final String? routeNamePrefix; - final String? routerClassName; - - RouterConfig({ - this.generateNavigationHelper, - this.routes, - this.globalRouteConfig, - this.routesClassName, - this.routeNamePrefix, - this.routerClassName, - }); - - RouterConfig copyWith({ - bool? generateNavigationHelper, - List? routes, - RouteConfig? globalRouteConfig, - String? routesClassName, - String? routeNamePrefix, - String? routerClassName, - }) { - return RouterConfig( - generateNavigationHelper: - generateNavigationHelper ?? this.generateNavigationHelper, - routes: routes ?? this.routes, - globalRouteConfig: globalRouteConfig ?? this.globalRouteConfig, - routesClassName: routesClassName ?? this.routesClassName, - routeNamePrefix: routeNamePrefix ?? this.routeNamePrefix, - routerClassName: routerClassName ?? this.routerClassName, - ); - } - - List get subRouters => - routes - ?.where((e) => e.routerConfig != null) - .map((e) => e.routerConfig) - .toList() ?? - []; - - List get collectAllRoutersIncludingParent => subRouters.fold( - [this], (all, e) => all..addAll(e!.collectAllRoutersIncludingParent)); - - @override - String toString() { - return 'RouterConfig{routes: $routes, routesClassName: $routesClassName, routerClassName: $routerClassName}'; - } -} - -class RouterConfigResolver { - final ImportResolver _importResolver; - - RouterConfigResolver(this._importResolver); - - Future resolve(ConstantReader stackedApp) async { - var globalRouteConfig = RouteConfig(); - if (stackedApp.instanceOf(TypeChecker.fromRuntime(CupertinoRouter))) { - globalRouteConfig.routeType = RouteType.cupertino; - } else if (stackedApp.instanceOf(TypeChecker.fromRuntime(AdaptiveRouter))) { - globalRouteConfig.routeType = RouteType.adaptive; - } else if (stackedApp.instanceOf(TypeChecker.fromRuntime(CustomRouter))) { - globalRouteConfig.routeType = RouteType.custom; - globalRouteConfig.durationInMilliseconds = - stackedApp.peek('durationInMilliseconds')?.intValue; - globalRouteConfig.reverseDurationInMilliseconds = - stackedApp.peek('reverseDurationInMilliseconds')?.intValue; - globalRouteConfig.customRouteOpaque = - stackedApp.peek('opaque')?.boolValue; - globalRouteConfig.customRouteBarrierDismissible = - stackedApp.peek('barrierDismissible')?.boolValue; - final function = - stackedApp.peek('transitionsBuilder')?.objectValue.toFunctionValue(); - if (function != null) { - final displayName = function.displayName.replaceFirst(RegExp('^_'), ''); - final functionName = function.isStatic - ? '${function.enclosingElement.displayName}.$displayName' - : displayName; - - var import; - if (function.enclosingElement.name != 'TransitionsBuilders') { - import = _importResolver.resolve(function); - } - globalRouteConfig.transitionBuilder = - CustomTransitionBuilder(functionName, import); - } - } - var generateNavigationExt = - stackedApp.peek('generateNavigationHelperExtension')?.boolValue ?? - false; - var routeNamePrefix = stackedApp.peek('routePrefix')?.stringValue ?? '/'; - var routesClassName = - stackedApp.peek('routesClassName')?.stringValue ?? 'Routes'; - - final stackedRoutes = stackedApp.read('routes').listValue; - - var routerConfig = RouterConfig( - globalRouteConfig: globalRouteConfig, - routerClassName: 'StackedRouter', - routesClassName: routesClassName, - routeNamePrefix: routeNamePrefix, - generateNavigationHelper: generateNavigationExt, - ); - - var routes = await _resolveRoutes(routerConfig, stackedRoutes); - return routerConfig.copyWith(routes: routes); - } - - Future> _resolveRoutes( - RouterConfig routerConfig, - List routesList, - ) async { - var routeResolver = RouteConfigResolver(routerConfig, _importResolver); - final routes = []; - for (var entry in routesList) { - var routeReader = ConstantReader(entry); - RouteConfig route; - route = await routeResolver.resolve(routeReader); - routes.add(route); - - var children = routeReader.peek('children')?.listValue; - if (children?.isNotEmpty == true) { - var name = capitalize(route.name); - var subRouterConfig = routerConfig.copyWith( - routerClassName: '${name}Router', - routesClassName: '${name}Routes', - ); - var routes = await _resolveRoutes(subRouterConfig, children!); - route.routerConfig = subRouterConfig.copyWith(routes: routes); - } - } - return routes; - } -} diff --git a/packages/stacked_generator/lib/src/generators/router/stacked_router_generator.dart b/packages/stacked_generator/lib/src/generators/router/stacked_router_generator.dart deleted file mode 100644 index 5875758ad..000000000 --- a/packages/stacked_generator/lib/src/generators/router/stacked_router_generator.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:analyzer/dart/element/element.dart'; -import 'package:stacked/stacked_annotations.dart'; -import 'package:stacked_generator/import_resolver.dart'; -import 'package:stacked_generator/route_config_resolver.dart'; -import 'package:stacked_generator/src/generators/router/router_class_generator.dart'; -import 'package:build/build.dart'; -import 'package:source_gen/source_gen.dart'; - -class StackedRouterGenerator extends GeneratorForAnnotation { - @override - dynamic generateForAnnotatedElement( - Element element, - ConstantReader annotation, - BuildStep buildStep, - ) async { - var libs = await buildStep.resolver.libraries.toList(); - var importResolver = ImportResolver(libs, element.source?.uri.path ?? ''); - - var routerResolver = RouterConfigResolver(importResolver); - final routerConfig = await routerResolver.resolve( - annotation, - ); - - return RouterClassGenerator(routerConfig).generate(); - } -} diff --git a/packages/stacked_generator/lib/utils.dart b/packages/stacked_generator/lib/utils.dart deleted file mode 100644 index 206201557..000000000 --- a/packages/stacked_generator/lib/utils.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/dart/element/type.dart'; -import 'package:source_gen/source_gen.dart'; - -String toLowerCamelCase(String s) { - if (s.length < 2) return s.toLowerCase(); - return s[0].toLowerCase() + s.substring(1); -} - -String capitalize(String s) { - if (s.length < 2) return s.toUpperCase(); - return s[0].toUpperCase() + s.substring(1); -} - -String toKababCase(String s) { - return s.replaceAllMapped(RegExp('(.+?)([A-Z])'), - (match) => '${match.group(1)}-${match.group(2)}'.toLowerCase()); -} - -String toDisplayString(DartType e, {bool withNullability = false}) { - return e.getDisplayString(withNullability: withNullability); -} - -void throwIf(bool condition, String message, - {Element? element, String todo = ''}) { - if (condition) { - throwError(message, todo: todo, element: element); - } -} - -void throwError(String message, {Element? element, String todo = ''}) { - throw InvalidGenerationSourceError( - message, - todo: todo, - element: element, - ); -} diff --git a/packages/stacked_generator/pubspec.yaml b/packages/stacked_generator/pubspec.yaml deleted file mode 100644 index ee53c201e..000000000 --- a/packages/stacked_generator/pubspec.yaml +++ /dev/null @@ -1,26 +0,0 @@ -name: stacked_generator -description: Stacked Generator is a package dedicated to reduce the boilerplate required to setup a stacked application -version: 0.6.2 -homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_generator - -environment: - sdk: ">=2.12.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - - build: ^2.2.1 - source_gen: ^1.2.1 - analyzer: ^3.1.0 - path: ^1.8.0 - # logger: ^1.0.0 - recase: ^4.0.0 - stacked: - ^2.3.0 - # path: ../stacked - -dev_dependencies: - build_runner: ^2.0.6 - source_gen_test: ^1.0.0 - test: diff --git a/packages/stacked_generator/test/samples/router.dart b/packages/stacked_generator/test/samples/router.dart deleted file mode 100644 index 510449d30..000000000 --- a/packages/stacked_generator/test/samples/router.dart +++ /dev/null @@ -1,35 +0,0 @@ -//@ShouldGenerate(output) -//@ShouldThrow('Class name must be prefixed with \$') -//@MaterialAutoRouter() -//class Router { -// @initial -// HomeScreen homeScreen; -//} -// -//class HomeScreen{} - -const output = ''' -import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:auto_route/auto_route.dart'; -import 'package:__test__/router_base.dart'; - -abstract class Routes { - static const homeScreen = '/'; -} - -class Router extends RouterBase { - @override - Route onGenerateRoute(RouteSettings settings) { - switch (settings.name) { - case Routes.homeScreen: - return PageRouteBuilder( - pageBuilder: (context, animation, secondaryAnimation) => HomeScreen(), - settings: settings, - ); - default: - return unknownRoutePage(settings.name); - } - } -} -'''; diff --git a/packages/stacked_generator/test/samples/router_exceptions_samples.dart b/packages/stacked_generator/test/samples/router_exceptions_samples.dart deleted file mode 100644 index b5d264066..000000000 --- a/packages/stacked_generator/test/samples/router_exceptions_samples.dart +++ /dev/null @@ -1,30 +0,0 @@ -//import 'package:auto_route/auto_route_annotations.dart'; -//import 'package:source_gen_test/annotations.dart'; -// -//@ShouldThrow('Class name must be prefixed with \$') -//@MaterialAutoRouter() -//class InvalidRouterClassNamePrefix {} -// -//@ShouldThrow(r'invalidRouterElement is not a class element') -//@MaterialAutoRouter() -//void invalidRouterElement() {} -// -//@ShouldThrow('There can be only one initial route per router') -//@MaterialAutoRouter() -//class $MultiInitialAnnotationsRouter { -// @initial -// FakeScreen fakeScreen; -// @initial -// SecondScreen secondScreen; -//} -// -//class FakeScreen {} -//class SecondScreen {} -// -//@ShouldThrow('UnknowRoute must have a defualt constructor with a positional String Parameter,' -// ' MyUnknownRoute(String routeName') -//@MaterialAutoRouter() -//class $InvalidUnknownRouteClass { -// @unknownRoute -// FakeScreen fakeScreen; -//} diff --git a/packages/stacked_gql/.gitignore b/packages/stacked_gql/.gitignore deleted file mode 100644 index a247422ef..000000000 --- a/packages/stacked_gql/.gitignore +++ /dev/null @@ -1,75 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Flutter.podspec -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/ephemeral -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/Flutter/flutter_export_environment.sh -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 diff --git a/packages/stacked_gql/.metadata b/packages/stacked_gql/.metadata deleted file mode 100644 index d536721aa..000000000 --- a/packages/stacked_gql/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 - channel: stable - -project_type: package diff --git a/packages/stacked_gql/CHANGELOG.md b/packages/stacked_gql/CHANGELOG.md deleted file mode 100644 index 761894f12..000000000 --- a/packages/stacked_gql/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -## 0.0.1 - -- initial release. diff --git a/packages/stacked_gql/LICENSE b/packages/stacked_gql/LICENSE deleted file mode 100644 index 9c50c4709..000000000 --- a/packages/stacked_gql/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Dane Mackier and other contributors - -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: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -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. diff --git a/packages/stacked_gql/README.md b/packages/stacked_gql/README.md deleted file mode 100644 index 1d878af9b..000000000 --- a/packages/stacked_gql/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# stacked_gql - -A package that improves the usability of gql in flutter applications. diff --git a/packages/stacked_gql/lib/src/graph_ql_base_api.dart b/packages/stacked_gql/lib/src/graph_ql_base_api.dart deleted file mode 100644 index 4fb0c2211..000000000 --- a/packages/stacked_gql/lib/src/graph_ql_base_api.dart +++ /dev/null @@ -1,232 +0,0 @@ -import 'package:graphql/client.dart'; -import 'package:graphql_flutter/graphql_flutter.dart'; -import 'package:logger/logger.dart'; - -import 'graph_ql_parser.dart'; - -abstract class GraphQlBaseApi { - GraphQLClient get client; - GraphQLResponseParser get responseParser; - Logger? get baseLogger => null; - - Future ensureAuthCookieIsSet(); - - /// Runs a GqlQuery and parses the response with the assumption of it being a single - /// result and NOT a collection. For fetching and parsing a collection use [runGqlListQuery] - Future runGqlQuery({ - required String query, - String? actionName, - bool logResponseData = false, - Map variables = const {}, - Function(QueryResult response)? onRawResponse, - }) async { - return _runGQLRequest( - query: query, - parseData: (data) => responseParser.parseSingleResponse(data), - actionName: actionName, - variables: variables, - logResponseData: logResponseData, - onRawResponse: onRawResponse, - ); - } - - /// Runs a gql query and parses the response with the assumption that it will return a - /// list of information. - /// - /// The list can be in 1 of two forms: - /// - [GqlResultType.PaginatedQuery]: This provides you with a response structure contains edges - /// with the nodes being the actual objects - /// - [GqlResultType.PlainQueryList]: This is a response where the first entry in the data object - /// is the list of results. - Future> runGqlListQuery({ - required String query, - String? actionName, - bool logResponseData = false, - Map variables = const {}, - Function(QueryResult response)? onRawResponse, - }) async { - return _runGQLRequest>( - query: query, - parseData: (data) => responseParser.parseListResponse(data), - actionName: actionName, - logResponseData: logResponseData, - variables: variables, - onRawResponse: onRawResponse, - ); - } - - Future _runGQLRequest({ - required String query, - required T Function(Map?) parseData, - String? actionName, - bool logResponseData = false, - Map variables = const {}, - Function(QueryResult response)? onRawResponse, - }) async { - final functionIdentity = actionName ?? query; - if (await (ensureAuthCookieIsSet())) { - baseLogger?.v('REQUEST:$actionName - query:$query'); - var response = await client.query( - QueryOptions( - document: gql(query), - variables: variables, - fetchPolicy: FetchPolicy.networkOnly, - cacheRereadPolicy: CacheRereadPolicy.ignoreAll, - ), - ); - onRawResponse?.call(response); - baseLogger?.v( - 'RESPONSE:$actionName - hasData: ${response.data != null} ${logResponseData ? "data:${response.data}" : ''}'); - - if (!response.hasException) { - try { - return parseData(response.data); - } catch (e, stacktrace) { - baseLogger?.e('$functionIdentity failed: $e'); - - throw GraphQlException( - message: e.toString(), - query: query, - queryName: functionIdentity, - stackTrace: stacktrace, - ); - } - } else { - throw _getErrorExceptionFromResponse( - functionIdentity: functionIdentity, - mutation: query, - response: response, - ); - } - } else { - var error = 'Cookies are invalid'; - baseLogger?.e(error); - throw GraphQlException( - message: error, - query: query, - queryName: functionIdentity, - ); - } - } - - Future runGqlMutation({ - required String mutation, - required T Function(Map?) parseData, - Map variables = const {}, - String? actionName, - bool logResponseData = false, - Function(QueryResult response)? onRawResponse, - }) async { - final functionIdentity = actionName ?? mutation; - if (await (ensureAuthCookieIsSet())) { - final MutationOptions options = MutationOptions( - document: gql(mutation), - variables: variables, - cacheRereadPolicy: CacheRereadPolicy.ignoreAll, - ); - - final QueryResult response = await client.mutate(options); - onRawResponse?.call(response); - - baseLogger?.v( - 'RESPONSE:$actionName - hasData: ${response.data != null} ${logResponseData ? "data:${response.data}" : ''}'); - - if (!response.hasException) { - try { - baseLogger?.i(response.data!); - final data = responseParser.parseMutationResponse(response.data!); - return parseData(data); - } catch (e, stackTrack) { - baseLogger?.e('$functionIdentity failed: $e'); - - throw GraphQlException( - message: e.toString(), - query: mutation, - queryName: functionIdentity, - stackTrace: stackTrack, - ); - } - } else { - if (response.exception != null) { - throw _getErrorExceptionFromResponse( - functionIdentity: functionIdentity, - mutation: mutation, - response: response, - ); - } - - throw GraphQlException( - message: 'Unkown error has occured', - query: mutation, - queryName: functionIdentity, - stackTrace: StackTrace.current, - ); - } - } else { - var error = 'Cookies are invalid'; - baseLogger?.e(error); - throw GraphQlException( - message: error, - query: mutation, - queryName: functionIdentity, - ); - } - } - - GraphQlException _getErrorExceptionFromResponse({ - required QueryResult response, - required String functionIdentity, - required String mutation, - }) { - final gqlException = response.exception!; - var finalErrorMessage = ''; - - if (gqlException.linkException != null) { - finalErrorMessage += - 'CLIENT_EXCEPTION: ${gqlException.linkException.toString()}\n'; - } - - if (gqlException.graphqlErrors.isNotEmpty) { - for (final gqlError in gqlException.graphqlErrors) { - var statusCode = 0; - var extentionError; - final errorMessage = gqlError.message; - - if (gqlError.extensions != null) { - statusCode = gqlError.extensions!['code'] ?? 0; - extentionError = gqlError.extensions!['error']; - } - baseLogger?.e( - '$functionIdentity: StatusCode:$statusCode - $errorMessage, ${extentionError ?? ''}'); - - finalErrorMessage = errorMessage; - baseLogger?.v(finalErrorMessage); - } - } - - return GraphQlException( - message: finalErrorMessage, - query: mutation, - queryName: functionIdentity, - stackTrace: StackTrace.current, - ); - } -} - -class GraphQlException implements Exception { - final String message; - final String? query; - final String? queryName; - final StackTrace? stackTrace; - GraphQlException({ - required this.message, - this.stackTrace, - this.query, - this.queryName, - }); - - @override - String toString() { - return 'GraphQlException: $message\nqueryName:$queryName\nquery:$query\stackTrace:$stackTrace'; - } -} diff --git a/packages/stacked_gql/lib/src/graph_ql_parser.dart b/packages/stacked_gql/lib/src/graph_ql_parser.dart deleted file mode 100644 index 6217a19d2..000000000 --- a/packages/stacked_gql/lib/src/graph_ql_parser.dart +++ /dev/null @@ -1,153 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'graph_ql_result_type.dart'; - -typedef StringTypeConverters = Map; -typedef MapTypeConverters = Map)>; - -/// A parser that contains helper functions for turning your graphQL response into domain specific models -class GraphQLResponseParser { - final MapTypeConverters mapTypeConverters; - final StringTypeConverters? stringTypeConverters; - - const GraphQLResponseParser({ - this.mapTypeConverters = const {}, - this.stringTypeConverters, - }); - - /// Takes in a raw GraphQL Query and seriealises it to the type passed in. - T parseSingleResponse(Map? data) { - var typeConverter = mapTypeConverters[T]; - - if (typeConverter == null) { - throw Exception( - 'No type converter defined for $T register one in GraphQLResponseParser'); - } - - if (data == null) { - throw Exception('Data to parse cannot be null'); - } - - data = typeNameRemover(data); - - var dataType = determineQueryType(data); - - switch (dataType) { - case GqlResultType.PlainQuery: - return typeConverter(data[data.keys.first]) as T; - case GqlResultType.Plain: - default: - return typeConverter(data) as T; - } - } - - /// Takes in a raw GraphQl ressponse and parses it into a list of results of [T] - List parseListResponse(data) { - data = typeNameRemover(data); - - var mapTypeConverter = mapTypeConverters[T]; - var stringTypeConverter = stringTypeConverters?[T]; - - if (mapTypeConverter == null && stringTypeConverter == null) { - throw Exception( - 'No type converter defined for $T register one in GraphQLResponseParser'); - } - - var dataType = determineQueryType(data); - - switch (dataType) { - case GqlResultType.PaginatedQuery: - var edges = data[data.keys.first]['edges']; - List results = []; - for (var edge in edges) { - var node = edge['node']; - results.add(mapTypeConverter?.call(node) as T); - } - return results; - case GqlResultType.PlainQueryList: - default: - var resultsAsList = data[data.keys.first] as List; - List results = []; - for (var result in resultsAsList) { - final convertedValue = mapTypeConverter != null - ? mapTypeConverter(result) as T - : stringTypeConverter!(result) as T; - - results.add(convertedValue); - } - return results; - } - } - - @visibleForTesting - GqlResultType determineQueryType(Map data) { - var isQueryResponse = _checkIfQuery(data); - - if (isQueryResponse) { - var isPaginated = - (data[data.keys.first] as Map).containsKey('edges'); - if (isPaginated) { - return GqlResultType.PaginatedQuery; - } - return GqlResultType.PlainQuery; - } - - var isListQuery = _checkIfListQuery(data); - if (isListQuery) { - return GqlResultType.PlainQueryList; - } - - return GqlResultType.Plain; - } - - Map typeNameRemover(Map data) { - if (data.containsKey('__typename')) { - data.removeWhere((key, value) => key == '__typename'); - return data; - } else { - return data; - } - } - - /// A response from a query will always have at the least the first child as the map - /// with the actual response. If it's not a map it's most likely not a query - bool _checkIfQuery(Map data) { - try { - // ignore: unnecessary_statements - (data[data.keys.first] as Map?); - return true; - } catch (_) { - return false; - } - } - - bool _checkIfListQuery(Map data) { - try { - // ignore: unnecessary_statements - (data[data.keys.first] as List?); - return true; - } catch (_) { - return false; - } - } - - bool _mutationResponseIsUnderMutationName(Map data) { - try { - // ignore: unnecessary_statements - (data[data.keys.first] as Map?); - return true; - } catch (_) { - return false; - } - } - - /// Parses a response from a mutation and returns a clean version of the data without - /// __typename. We also return only the data under the mutation name key. - Map parseMutationResponse(Map data) { - data = typeNameRemover(data); - if (_mutationResponseIsUnderMutationName(data)) { - return data[data.keys.first]; - } - - return data; - } -} diff --git a/packages/stacked_gql/lib/src/graph_ql_result_type.dart b/packages/stacked_gql/lib/src/graph_ql_result_type.dart deleted file mode 100644 index 92e9fcfeb..000000000 --- a/packages/stacked_gql/lib/src/graph_ql_result_type.dart +++ /dev/null @@ -1,13 +0,0 @@ -enum GqlResultType { - /// When the results returned is at the root of the data child - Plain, - - /// when the result returned is in a child with the query as the title - PlainQuery, - - // When the result returned as the query title is a list of objects - PlainQueryList, - - /// When the result returned has edges and nodes for traversal - PaginatedQuery, -} diff --git a/packages/stacked_gql/lib/stacked_gql.dart b/packages/stacked_gql/lib/stacked_gql.dart deleted file mode 100644 index 6a61baf6c..000000000 --- a/packages/stacked_gql/lib/stacked_gql.dart +++ /dev/null @@ -1,4 +0,0 @@ -library stacked_gql; - -export 'src/graph_ql_base_api.dart'; -export 'src/graph_ql_parser.dart'; diff --git a/packages/stacked_gql/pubspec.yaml b/packages/stacked_gql/pubspec.yaml deleted file mode 100644 index 0416be878..000000000 --- a/packages/stacked_gql/pubspec.yaml +++ /dev/null @@ -1,59 +0,0 @@ -name: stacked_gql -description: A package that improves the usability of gql in flutter applications. -version: 0.0.1 -homepage: - -environment: - sdk: ">=2.13.0 <3.0.0" - flutter: ">=1.17.0" - -dependencies: - flutter: - sdk: flutter - - # graphQL - graphql_flutter: ^5.0.0 - - # logger - logger: ^1.0.0 - -dev_dependencies: - flutter_test: - sdk: flutter - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stacked_gql/test/app_models.dart b/packages/stacked_gql/test/app_models.dart deleted file mode 100644 index 21c520d49..000000000 --- a/packages/stacked_gql/test/app_models.dart +++ /dev/null @@ -1,194 +0,0 @@ -class Company { - String? name; - String? catchPhrase; - String? bs; - Company({ - this.name, - this.catchPhrase, - this.bs, - }); - - Company copyWith({ - String? name, - String? catchPhrase, - String? bs, - }) { - return Company( - name: name ?? this.name, - catchPhrase: catchPhrase ?? this.catchPhrase, - bs: bs ?? this.bs, - ); - } - - Map toMap() { - return { - 'name': name, - 'catchPhrase': catchPhrase, - 'bs': bs, - }; - } - - factory Company.fromMap(Map map) { - return Company( - name: map['name'], - catchPhrase: map['catchPhrase'], - bs: map['bs'], - ); - } - - Map toJson() => toMap(); - - factory Company.fromJson(Map source) { - return Company.fromMap(source); - } - - @override - String toString() => - 'Company(name: $name, catchPhrase: $catchPhrase, bs: $bs)'; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is Company && - other.name == name && - other.catchPhrase == catchPhrase && - other.bs == bs; - } - - @override - int get hashCode => name.hashCode ^ catchPhrase.hashCode ^ bs.hashCode; -} - -class Post { - int? userId; - int? id; - String? title; - String? body; - Post({ - this.userId, - this.id, - this.title, - this.body, - }); - - Post copyWith({ - int? userId, - int? id, - String? title, - String? body, - }) { - return Post( - userId: userId ?? this.userId, - id: id ?? this.id, - title: title ?? this.title, - body: body ?? this.body, - ); - } - - Map toMap() { - return { - 'userId': userId, - 'id': id, - 'title': title, - 'body': body, - }; - } - - factory Post.fromMap(Map map) { - return Post( - userId: map['userId'], - id: map['id'], - title: map['title'], - body: map['body'], - ); - } - - Map toJson() => toMap(); - - factory Post.fromJson(Map source) { - return Post.fromMap(source); - } - - @override - String toString() { - return 'Post(userId: $userId, id: $id, title: $title, body: $body)'; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is Post && - other.userId == userId && - other.id == id && - other.title == title && - other.body == body; - } - - @override - int get hashCode { - return userId.hashCode ^ id.hashCode ^ title.hashCode ^ body.hashCode; - } -} - -class Album { - int? userId; - int? id; - String? title; - Album({ - this.userId, - this.id, - this.title, - }); - - Album copyWith({ - int? userId, - int? id, - String? title, - }) { - return Album( - userId: userId ?? this.userId, - id: id ?? this.id, - title: title ?? this.title, - ); - } - - Map toMap() { - return { - 'userId': userId, - 'id': id, - 'title': title, - }; - } - - factory Album.fromMap(Map map) { - return Album( - userId: map['userId'], - id: map['id'], - title: map['title'], - ); - } - - Map toJson() => toMap(); - - factory Album.fromJson(Map source) { - return Album.fromMap(source); - } - - @override - String toString() => 'Album(userId: $userId, id: $id, title: $title)'; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is Album && - other.userId == userId && - other.id == id && - other.title == title; - } - - @override - int get hashCode => userId.hashCode ^ id.hashCode ^ title.hashCode; -} diff --git a/packages/stacked_gql/test/fake_data.dart b/packages/stacked_gql/test/fake_data.dart deleted file mode 100644 index 7e637c104..000000000 --- a/packages/stacked_gql/test/fake_data.dart +++ /dev/null @@ -1,110 +0,0 @@ -const Map company = { - 'data': { - "name": "Romaguera-Crona", - "catchPhrase": "Multi-layered client-server neural-net", - "bs": "harness real-time e-markets" - } -}; - -const Map posts = { - "data": { - "availablePosts": { - "posts": [ - { - "userId": 1, - "id": 1, - "title": - "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", - "body": - "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" - }, - { - "userId": 1, - "id": 2, - "title": "qui est esse", - "body": - "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla" - }, - { - "userId": 1, - "id": 3, - "title": - "ea molestias quasi exercitationem repellat qui ipsa sit aut", - "body": - "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut" - }, - { - "userId": 1, - "id": 4, - "title": "eum et est occaecati", - "body": - "ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit" - }, - ] - } - } -}; - -const Map albums = { - 'data': { - 'albums': { - "edges": [ - { - "node": { - "userId": 1, - "id": 2, - "title": "sunt qui excepturi placeat culpa" - } - }, - { - "node": { - "userId": 1, - "id": 5, - "title": "eaque aut omnis a", - } - } - ] - } - } -}; - -const Map postLits = { - "data": { - "availablePosts": [ - { - "posts": [ - { - "userId": 1, - "id": 1, - "title": - "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", - "body": - "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" - }, - { - "userId": 1, - "id": 2, - "title": "qui est esse", - "body": - "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla" - }, - { - "userId": 1, - "id": 3, - "title": - "ea molestias quasi exercitationem repellat qui ipsa sit aut", - "body": - "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut" - }, - { - "userId": 1, - "id": 4, - "title": "eum et est occaecati", - "body": - "ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit" - }, - ] - } - ] - } -}; diff --git a/packages/stacked_gql/test/graphql_parser_test.dart b/packages/stacked_gql/test/graphql_parser_test.dart deleted file mode 100644 index 73ded81fa..000000000 --- a/packages/stacked_gql/test/graphql_parser_test.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:stacked_gql/src/graph_ql_result_type.dart'; -import 'package:stacked_gql/stacked_gql.dart'; -import 'app_models.dart'; -import 'fake_data.dart'; -import 'type_converters.dart'; - -void main() { - group('GraphQLResultParser', () { - group('parseResponse -', () { - test( - 'When GQL response has requested data should returned serialised response', - () { - var parser = - GraphQLResponseParser(mapTypeConverters: maptypeConverters); - var result = parser.parseSingleResponse(company['data']); - expect(Company.fromJson(company['data']), result); - }); - - test('Post - When posts response should serialise all posts properly', - () { - var parser = - GraphQLResponseParser(mapTypeConverters: maptypeConverters); - var result = parser.parseListResponse(postLits['data']); - expect(result.isNotEmpty, true); - }); - - test( - 'When GQL response data is paginated serialise edge nodes as list of Type', - () { - var parser = - GraphQLResponseParser(mapTypeConverters: maptypeConverters); - var result = parser.parseListResponse(albums['data']); - expect([ - Album.fromJson(albums['data']['albums']['edges'][0]['node']), - Album.fromJson(albums['data']['albums']['edges'][1]['node']), - ], result); - }); - }); - - group('determinQueryType -', () { - test('Given a non query request, return Plain as query type', () { - var parser = - GraphQLResponseParser(mapTypeConverters: maptypeConverters); - var result = parser.determineQueryType(company['data']); - expect(result, GqlResultType.Plain); - }); - test('Given a paged query result, return PaginatedQuery as query type', - () { - var parser = - GraphQLResponseParser(mapTypeConverters: maptypeConverters); - var result = parser.determineQueryType(albums['data']); - expect(result, GqlResultType.PaginatedQuery); - }); - test('Given a plain query result, return PlainQuery as query type', () { - var parser = - GraphQLResponseParser(mapTypeConverters: maptypeConverters); - var result = parser.determineQueryType(posts['data']); - expect(result, GqlResultType.PlainQuery); - }); - - test( - 'Given a plan query list result, return PlainQueryList as query type', - () { - var parser = - GraphQLResponseParser(mapTypeConverters: maptypeConverters); - var result = parser.determineQueryType(postLits['data']); - expect(result, GqlResultType.PlainQueryList); - }); - }); - }); -} diff --git a/packages/stacked_gql/test/type_converters.dart b/packages/stacked_gql/test/type_converters.dart deleted file mode 100644 index 9acb3aa60..000000000 --- a/packages/stacked_gql/test/type_converters.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'app_models.dart'; - -Map)> maptypeConverters = { - Company: (data) => Company.fromJson(data), - Post: (data) => Post.fromJson(data), - Album: (data) => Album.fromJson(data), -}; diff --git a/packages/stacked_hooks/.gitignore b/packages/stacked_hooks/.gitignore deleted file mode 100644 index aadd0814d..000000000 --- a/packages/stacked_hooks/.gitignore +++ /dev/null @@ -1,76 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Flutter.podspec -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/Flutter/flutter_export_environment.sh -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages -pubspec.lock diff --git a/packages/stacked_hooks/.metadata b/packages/stacked_hooks/.metadata deleted file mode 100644 index 3b511f7d5..000000000 --- a/packages/stacked_hooks/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 - channel: stable - -project_type: package diff --git a/packages/stacked_hooks/CHANGELOG.md b/packages/stacked_hooks/CHANGELOG.md deleted file mode 100644 index 65e99a6a8..000000000 --- a/packages/stacked_hooks/CHANGELOG.md +++ /dev/null @@ -1,48 +0,0 @@ -## 0.2.1+1 - -- Upgraded the dependency on `provider` to 6.0.0 - -## 0.2.1 - -- `flutter_hooks`: ^0.16.0 -> ^0.17.0 - -## 0.1.4 - -- `flutter_hooks`: ^0.15.0 -> ^0.16.0 -- `provider`: ^4.3.1 -> ^5.0.0 - -## 0.1.3+2 - -- Update flutter_hooks 0.15.0 - -## 0.1.3+1 - -- Update flutter_hooks - -## 0.1.3 - -- Update flutter_hooks and provider - -## 0.1.2 - -- Hook widget has a const constructor - -## 0.1.1+3 - -- Changelog style updates - -## 0.1.1+2 - -- Added key to HookViewModelWidget constructor - -## 0.1.1+1 - -- Updates webpage for package - -## 0.1.1 - -- Export Hooks file - -## 0.1.0 - -- Adds HookViewModelWidget diff --git a/packages/stacked_hooks/LICENSE b/packages/stacked_hooks/LICENSE deleted file mode 100644 index 9c50c4709..000000000 --- a/packages/stacked_hooks/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Dane Mackier and other contributors - -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: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -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. diff --git a/packages/stacked_hooks/README.md b/packages/stacked_hooks/README.md deleted file mode 100644 index 768bf53fc..000000000 --- a/packages/stacked_hooks/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# Stacked Hooks - -This package contains widgets that allow you to use the Flutter Hooks package with the ViewModelWidget in the stacked architecture. - -## HookViewModelWidget - -The `ViewModelWidget` is an implementation of a widget class that returns a value provided by `Provider` as a parameter in the build function of the widget. This allows for easier consumption and use of ViewModel without boilerplate. The `HookViewModelWidget` allows you to use this widget and make use of Flutter Hooks inside the build function. This is very useful when you want to use `TextEditing` controllers and you're implementing this architecture. - -```dart -// View that creates and provides the viewmodel -class HookViewModelWidgetExample extends StatelessWidget { - const HookViewModelWidgetExample({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ViewModelBuilder.nonReactive( - builder: (context, model, child) => Scaffold( - body: Center( - child: _HookForm(), - )), - viewModelBuilder: () => HomeViewModel(), - ); - } -} - -// Form that makes use of the ViewModel provided above but also makes use of hooks -class _HookForm extends HookViewModelWidget { - @override - Widget buildViewModelWidget(BuildContext context, HomeViewModel model) { - var title = useTextEditingController(); - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(model.title), - TextField( - controller: title, - onChanged: model.updateTile, - ) - ], - ); - } -} - -// ViewModel -class HomeViewModel extends BaseViewModel { - String title = 'default'; - - void updateTile(String value) { - title = value; - notifyListeners(); - } -} -``` diff --git a/packages/stacked_hooks/example/.gitignore b/packages/stacked_hooks/example/.gitignore deleted file mode 100644 index ae1f1838e..000000000 --- a/packages/stacked_hooks/example/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Web related -lib/generated_plugin_registrant.dart - -# Exceptions to above rules. -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/stacked_hooks/example/.metadata b/packages/stacked_hooks/example/.metadata deleted file mode 100644 index 4adf4bf02..000000000 --- a/packages/stacked_hooks/example/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 - channel: stable - -project_type: app diff --git a/packages/stacked_hooks/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/stacked_hooks/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt deleted file mode 100644 index 1656503f3..000000000 --- a/packages/stacked_hooks/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.example - -import androidx.annotation.NonNull; -import io.flutter.embedding.android.FlutterActivity -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugins.GeneratedPluginRegistrant - -class MainActivity: FlutterActivity() { - override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { - GeneratedPluginRegistrant.registerWith(flutterEngine); - } -} diff --git a/packages/stacked_hooks/example/android/app/src/profile/AndroidManifest.xml b/packages/stacked_hooks/example/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index c208884f3..000000000 --- a/packages/stacked_hooks/example/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/stacked_hooks/example/android/build.gradle b/packages/stacked_hooks/example/android/build.gradle deleted file mode 100644 index 3100ad2d5..000000000 --- a/packages/stacked_hooks/example/android/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -buildscript { - ext.kotlin_version = '1.3.50' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - jcenter() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/packages/stacked_hooks/example/ios/Flutter/AppFrameworkInfo.plist b/packages/stacked_hooks/example/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 6b4c0f78a..000000000 --- a/packages/stacked_hooks/example/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 8.0 - - diff --git a/packages/stacked_hooks/example/ios/Flutter/Debug.xcconfig b/packages/stacked_hooks/example/ios/Flutter/Debug.xcconfig deleted file mode 100644 index 592ceee85..000000000 --- a/packages/stacked_hooks/example/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/packages/stacked_hooks/example/ios/Flutter/Release.xcconfig b/packages/stacked_hooks/example/ios/Flutter/Release.xcconfig deleted file mode 100644 index 592ceee85..000000000 --- a/packages/stacked_hooks/example/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/packages/stacked_hooks/example/ios/Runner.xcodeproj/project.pbxproj b/packages/stacked_hooks/example/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 0d45b982d..000000000 --- a/packages/stacked_hooks/example/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,518 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B80C3931E831B6300D905FE /* App.framework */, - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1020; - ORGANIZATIONNAME = "The Chromium Authors"; - TargetAttributes = { - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/packages/stacked_hooks/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/stacked_hooks/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index a28140cfd..000000000 --- a/packages/stacked_hooks/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/stacked_hooks/example/lib/main.dart b/packages/stacked_hooks/example/lib/main.dart deleted file mode 100644 index 6a5581e9e..000000000 --- a/packages/stacked_hooks/example/lib/main.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'ui/views/hook_viewmodel_widget/hook_viewmodel_widget_example.dart'; - -void main() => runApp(MyApp()); - -class MyApp extends StatelessWidget { - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - home: HookViewModelWidgetExample(), - ); - } -} diff --git a/packages/stacked_hooks/example/lib/ui/views/hook_viewmodel_widget/home_viewmodel.dart b/packages/stacked_hooks/example/lib/ui/views/hook_viewmodel_widget/home_viewmodel.dart deleted file mode 100644 index e9de17383..000000000 --- a/packages/stacked_hooks/example/lib/ui/views/hook_viewmodel_widget/home_viewmodel.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:stacked/stacked.dart'; - -class HomeViewModel extends BaseViewModel { - String title = 'default'; - - void updateTile(String value) { - title = value; - notifyListeners(); - } -} diff --git a/packages/stacked_hooks/example/lib/ui/views/hook_viewmodel_widget/hook_viewmodel_widget_example.dart b/packages/stacked_hooks/example/lib/ui/views/hook_viewmodel_widget/hook_viewmodel_widget_example.dart deleted file mode 100644 index f7a6b4cd1..000000000 --- a/packages/stacked_hooks/example/lib/ui/views/hook_viewmodel_widget/hook_viewmodel_widget_example.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:stacked/stacked.dart'; -import 'package:stacked_hooks/stacked_hooks.dart'; -import 'home_viewmodel.dart'; - -class HookViewModelWidgetExample extends StatelessWidget { - const HookViewModelWidgetExample({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ViewModelBuilder.nonReactive( - builder: (context, model, child) => Scaffold( - body: Center( - child: _HookForm(), - )), - viewModelBuilder: () => HomeViewModel(), - ); - } -} - -class _HookForm extends HookViewModelWidget { - @override - Widget buildViewModelWidget(BuildContext context, HomeViewModel model) { - var title = useTextEditingController(); - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(model.title), - TextField( - controller: title, - onChanged: model.updateTile, - ) - ], - ); - } -} diff --git a/packages/stacked_hooks/example/pubspec.yaml b/packages/stacked_hooks/example/pubspec.yaml deleted file mode 100644 index 33039dffe..000000000 --- a/packages/stacked_hooks/example/pubspec.yaml +++ /dev/null @@ -1,76 +0,0 @@ -name: example -description: A new Flutter project. - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+1 - -environment: - sdk: ">=2.1.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.2 - stacked: - stacked_hooks: - path: ../ - flutter_hooks: - -dev_dependencies: - flutter_test: - sdk: flutter - - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stacked_hooks/example/test/widget_test.dart b/packages/stacked_hooks/example/test/widget_test.dart deleted file mode 100644 index 747db1da3..000000000 --- a/packages/stacked_hooks/example/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:example/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} diff --git a/packages/stacked_hooks/lib/src/_hooks_viewmodel_widget.dart b/packages/stacked_hooks/lib/src/_hooks_viewmodel_widget.dart deleted file mode 100644 index 3500f81bf..000000000 --- a/packages/stacked_hooks/lib/src/_hooks_viewmodel_widget.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:provider/provider.dart'; - -/// An implementation of the ViewModelWidget that allows you to use Hooks in the build -abstract class HookViewModelWidget extends HookWidget { - final bool reactive; - const HookViewModelWidget({Key? key, this.reactive = true}); - - @override - Widget build(BuildContext context) => - buildViewModelWidget(context, Provider.of(context, listen: reactive)); - - Widget buildViewModelWidget(BuildContext context, T viewModel); -} diff --git a/packages/stacked_hooks/lib/stacked_hooks.dart b/packages/stacked_hooks/lib/stacked_hooks.dart deleted file mode 100644 index 94db556b1..000000000 --- a/packages/stacked_hooks/lib/stacked_hooks.dart +++ /dev/null @@ -1,3 +0,0 @@ -library stacked_hooks; - -export 'src/_hooks_viewmodel_widget.dart'; \ No newline at end of file diff --git a/packages/stacked_hooks/pubspec.yaml b/packages/stacked_hooks/pubspec.yaml deleted file mode 100644 index 8f32209bf..000000000 --- a/packages/stacked_hooks/pubspec.yaml +++ /dev/null @@ -1,52 +0,0 @@ -name: stacked_hooks -description: Classes that help you make use of hooks when using the stacked package -version: 0.2.1+1 -homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_hooks - -environment: - sdk: '>=2.12.0 <3.0.0' - -dependencies: - flutter: - sdk: flutter - flutter_hooks: ^0.17.0 - provider: ^6.0.0 #5.0.0 - -dev_dependencies: - flutter_test: - sdk: flutter - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stacked_hooks/test/stacked_hooks_test.dart b/packages/stacked_hooks/test/stacked_hooks_test.dart deleted file mode 100644 index ea8f2c2c2..000000000 --- a/packages/stacked_hooks/test/stacked_hooks_test.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; - -import 'package:stacked_hooks/stacked_hooks.dart'; - -void main() {} diff --git a/packages/stacked_localisation/stacked_localisation/.gitignore b/packages/stacked_localisation/stacked_localisation/.gitignore deleted file mode 100644 index bb431f0d5..000000000 --- a/packages/stacked_localisation/stacked_localisation/.gitignore +++ /dev/null @@ -1,75 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Flutter.podspec -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/Flutter/flutter_export_environment.sh -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/stacked_localisation/stacked_localisation/.metadata b/packages/stacked_localisation/stacked_localisation/.metadata deleted file mode 100644 index eeda8507b..000000000 --- a/packages/stacked_localisation/stacked_localisation/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 8af6b2f038c1172e61d418869363a28dffec3cb4 - channel: stable - -project_type: package diff --git a/packages/stacked_localisation/stacked_localisation/CHANGELOG.md b/packages/stacked_localisation/stacked_localisation/CHANGELOG.md deleted file mode 100644 index a30b39df1..000000000 --- a/packages/stacked_localisation/stacked_localisation/CHANGELOG.md +++ /dev/null @@ -1,15 +0,0 @@ -## 0.2.2 - -- Cleaned up the initialisation logic to use the singleton pattern instead of relying on the user to call the function for setup - -## 0.2.1 - -- Fixes all replacement bug - -## 0.2.0 - -- Adds translation replacements - -## 0.1.0 - -- Provides Localisation Service for translating based on keys diff --git a/packages/stacked_localisation/stacked_localisation/LICENSE b/packages/stacked_localisation/stacked_localisation/LICENSE deleted file mode 100644 index 029d251d5..000000000 --- a/packages/stacked_localisation/stacked_localisation/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Dane Mackier and other contributors - -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: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -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. \ No newline at end of file diff --git a/packages/stacked_localisation/stacked_localisation/README.md b/packages/stacked_localisation/stacked_localisation/README.md deleted file mode 100644 index 016c65c74..000000000 --- a/packages/stacked_localisation/stacked_localisation/README.md +++ /dev/null @@ -1,172 +0,0 @@ -# Stacked Localisation [Not ready for production] - -A localisation solution specifically built for using with the [stacked package](https://pub.dev/packages/stacked) for state management. This comes in the form of a localisation service accessible to the ViewModels and other services which takes in a localisation key and returns the string (if any), as provided by the files that define the strings for each language. As all the other solutions provided by [FilledStacks](https://www.youtube.com/filledstacks) this solution aims to reduce the amount of code required for basic language setup, make it easier to understand and make the code more readable in the process. All of this is in hopes of creating a more maintainable code base to work with. - -## How does it work - -The localisation package is very simple. It provides you with a service from which you can request a translated string using a key. The keys map directly to your language files that should be placed in `assets/lang` as json files. The `stacked_localisation_generator` will then generate code for the keys defined in your language file. This will allow you to safely request keys without manually maintaining any of the keys. - -## Setup - -To use the localisation service there's a few things that has to be done. We start off by adding the stacked_localisation and the stacked_localisation_generator package (and build_runner if you don't already have it). - -```yaml -dependencies: - ... - stacked_localisation: - -dev_dependencies: - ... - build_runner: - stacked_localisation_generator: -``` - -Then we will add a new asset entry into pubspec.yaml that points to the `assets/lang` folder (You will create this folder if it doesn't exist). - -```yaml -assets: - - assets/lang/ -``` - -Then we will create the folders mentioned above and place your strings for a different language in there. Create a folder called assets in the root folder and inside that folder create a new folder called lang. Then create a new file inside it called en.json or en.yaml and place the following JSON in there. - -```json -{ - "HomeView": { - "title": "This is my Home", - "subtitle": "I live in this Home" - } -} -``` - -or for YAML - -```yaml -HomeView: - title: This is my Home - subtitle: I live in this Home -``` - -Then run `flutter pub run build_runner build --delete-conflicting-outputs` to generate the localisation_string_keys.dart file. This file will look like this. - -```dart -/// This code is generated. DO NOT edit by hand - -class HomeViewStrings { - static String title = 'HomeView.title'; - static String subtitle = 'HomeView.subtitle'; -} -``` - -It takes the name of the parent view and adds the word "Strings" behind it. This turns "HomeView" into "HomeViewStrings" and creates a property for each string value in that map. - -### Setup in code - -On the first line of the main function call the line `WidgetsFlutterBinding.ensureInitialized();` is required because we are making use of some plugins before the app runs which initialises all the plugin bindings. We will also change the main function to a Future and change our setupLocator function to a future as well and await on the setup call. - -```dart -Future main() async { - WidgetsFlutterBinding.ensureInitialized(); - await setupLocator(); - runApp(MyApp()); -} -``` - -Then you can open up your setup_locator.dart file and create an instance of the `LocationServices` then await the initialise call before registering it as a singleton. - -```dart -import 'package:get_it/get_it.dart'; -import 'package:stacked_localisation/stacked_localisation.dart'; -import 'package:stacked_services/stacked_services.dart'; - -GetIt locator = GetIt.instance; - -Future setupLocator() async { - var localisationService = await LocalisationService.getInstance(); - locator.registerSingleton(localisationService); - - locator.registerLazySingleton(() => NavigationService()); - ... -} -``` - -### Usage in Code - -The initialisation functionality will load the strings into the `LocalisationService` which is now accessible. Create a new view file, or in your current view file make the following adjustments. - -```dart -class HomeView extends StatelessWidget { - const HomeView({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - builder: (context, model, child) => Scaffold( - appBar: AppBar( - title: Text(model.translate(HomeViewStrings.title)), - ), - body: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(model.translate( - HomeViewStrings.subtitle, - )) - ], - ), - ), - viewModelBuilder: () => HomeViewModel(), - ); - } -} -``` - -You'll see the usage of the translate function on the model. Lets add that functionality. Open up your `ViewModel` file and add the `LocalisedClass` mixin to your `ViewModel` definition. - -```dart -class HomeViewModel extends BaseViewModel with LocalisedClass {} -``` - -This provides you with a translate function that will return the value associated with the key. That's it for the basic usage. - -## Outside of the code - -The language files added into the lang folder can be specific en_US.json or en_UK.json or it can be general en.json which will ensure all localisations starting with en will be given the strings defined in the en.json file. Another thing with the language files is to make sure all the files has the same strings. The localisation keys will be generated using the file that's first in the lang folder. If it's missing strings you won't be able to access them through the string key classes. - -## Dynamic values - -There is also support for dynamic values in the translations which are for values that you'd like to replace in the app. This is implemented through positional replacements. - -```json -{ - "CounterView": { - "timesCounted": "You have tapped {0} times" - } -} -``` - -Can be used using the following code - -```dart -translate(CounterViewStrings.timesCounted, replacements: [9]); // Returns 'You have tapped 9 times' -``` - -The replacements correlate to the index in the brackets so you can add multiple replacements for the same index or different values. An implementation of named replacements will be added if there is a need for it. - -## More Code - -The `LocalisedClass` mixin can be used in services as well, the exact same way. Which will allow you to throw localised exceptions or show localised dialogs easily as well. - -```dart -throw Exception(translate(ApiStrings.serialisationError)); - -// or - -_dialogSerice.showDialog(title: translate(HomeView.favoriteAddedTitle)); -``` - -If you have any requests, questions or pointers you can file an issue or [head over to our slack](https://join.slack.com/t/filledstacks/shared_invite/zt-8hae7yly-vjZX3sW5twN9v7DBlTsgrQ) and chat to use directly about improvements or just code in general. - -## Upcoming Features - -- [ ] Reload the language strings without having the restart the application. This will require adding something like the [LifeCycle manager shown here](https://youtu.be/NfvA-7-HzYk) to call the initialise function again which will load up the new strings and place them into memory. diff --git a/packages/stacked_localisation/stacked_localisation/example/.metadata b/packages/stacked_localisation/stacked_localisation/example/.metadata deleted file mode 100644 index ade6bc987..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 8af6b2f038c1172e61d418869363a28dffec3cb4 - channel: stable - -project_type: app diff --git a/packages/stacked_localisation/stacked_localisation/example/README.md b/packages/stacked_localisation/stacked_localisation/example/README.md deleted file mode 100644 index 68c7a19cc..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/README.md +++ /dev/null @@ -1,3 +0,0 @@ -## Stacked Localisation Example - -This example shows you the setup and usage of the stacked localisation package. In this example there are language files for 2 languages. English and Afrikaans. To test it out you should swap the language and restart the application. diff --git a/packages/stacked_localisation/stacked_localisation/example/android/.gitignore b/packages/stacked_localisation/stacked_localisation/example/android/.gitignore deleted file mode 100644 index bc2100d8f..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/android/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java diff --git a/packages/stacked_localisation/stacked_localisation/example/android/app/build.gradle b/packages/stacked_localisation/stacked_localisation/example/android/app/build.gradle deleted file mode 100644 index 320be2219..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/android/app/build.gradle +++ /dev/null @@ -1,63 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - compileSdkVersion 28 - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - lintOptions { - disable 'InvalidPackage' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.example" - minSdkVersion 16 - targetSdkVersion 28 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/AndroidManifest.xml b/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 55ca830c3..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79b..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 09d439148..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eeb..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/android/app/src/profile/AndroidManifest.xml b/packages/stacked_localisation/stacked_localisation/example/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index c208884f3..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/stacked_localisation/stacked_localisation/example/android/gradle.properties b/packages/stacked_localisation/stacked_localisation/example/android/gradle.properties deleted file mode 100644 index 38c8d4544..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/android/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.enableR8=true -android.useAndroidX=true -android.enableJetifier=true diff --git a/packages/stacked_localisation/stacked_localisation/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/stacked_localisation/stacked_localisation/example/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 296b146b7..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Jun 23 08:50:38 CEST 2017 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/packages/stacked_localisation/stacked_localisation/example/android/settings.gradle b/packages/stacked_localisation/stacked_localisation/example/android/settings.gradle deleted file mode 100644 index d3b6a4013..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/android/settings.gradle +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -include ':app' - -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() - -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } - -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/stacked_localisation/stacked_localisation/example/assets/lang/af.json b/packages/stacked_localisation/stacked_localisation/example/assets/lang/af.json deleted file mode 100644 index 7fb13391e..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/assets/lang/af.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "HomeView": { - "title": "Die is my Huis", - "subtitle": "Ek bly in die Huis", - "dialogButtonTitle": "Wys die dialoog", - "dialogTitle": "Die aksie was suksesvol", - "dialogDescription": "Dis aksie wat gedoen was het suksesvol klaar gemaak" - } -} \ No newline at end of file diff --git a/packages/stacked_localisation/stacked_localisation/example/assets/lang/en.json b/packages/stacked_localisation/stacked_localisation/example/assets/lang/en.json deleted file mode 100644 index ac4dc5392..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/assets/lang/en.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "HomeView": { - "title": "This is my Home", - "subtitle": "I live in this Home", - "dialogButtonTitle": "Show dialog", - "dialogTitle": "The action was successful", - "dialogDescription": "The action taken has completed successfully" - } -} \ No newline at end of file diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/.gitignore b/packages/stacked_localisation/stacked_localisation/example/ios/.gitignore deleted file mode 100644 index e96ef602b..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Flutter/Debug.xcconfig b/packages/stacked_localisation/stacked_localisation/example/ios/Flutter/Debug.xcconfig deleted file mode 100644 index 592ceee85..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Flutter/Release.xcconfig b/packages/stacked_localisation/stacked_localisation/example/ios/Flutter/Release.xcconfig deleted file mode 100644 index 592ceee85..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 5f1bf4c32..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,506 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1020; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a16e..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a16e..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/AppDelegate.swift b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/AppDelegate.swift deleted file mode 100644 index 70693e4a8..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -import UIKit -import Flutter - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fab2..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada472..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 28c6bf030..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 2ccbfd967..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index f091b6b0b..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cde12118..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e7e..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index dcdc2306c..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 2ccbfd967..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8f5..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b8609..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b8609..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d164a..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d39..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 6a84f41e1..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index d0e1f5853..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2fd..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725b7..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -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. \ No newline at end of file diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c7c..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Base.lproj/Main.storyboard b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c28516f..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Info.plist b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Info.plist deleted file mode 100644 index a060db61e..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Info.plist +++ /dev/null @@ -1,45 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - example - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - - diff --git a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Runner-Bridging-Header.h b/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 308a2a560..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/packages/stacked_localisation/stacked_localisation/example/lib/core/router.dart b/packages/stacked_localisation/stacked_localisation/example/lib/core/router.dart deleted file mode 100644 index 7483bb3c3..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/lib/core/router.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:auto_route/auto_route_annotations.dart'; -import 'package:example/ui/home/home_view.dart'; - -@MaterialAutoRouter(routes: [ - MaterialRoute(page: HomeView, initial: true), -]) -class $Router {} diff --git a/packages/stacked_localisation/stacked_localisation/example/lib/core/router.gr.dart b/packages/stacked_localisation/stacked_localisation/example/lib/core/router.gr.dart deleted file mode 100644 index 4af467e6f..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/lib/core/router.gr.dart +++ /dev/null @@ -1,37 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ************************************************************************** -// AutoRouteGenerator -// ************************************************************************** - -// ignore_for_file: public_member_api_docs - -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; - -import '../ui/home/home_view.dart'; - -class Routes { - static const String homeView = '/'; - static const all = { - homeView, - }; -} - -class Router extends RouterBase { - @override - List get routes => _routes; - final _routes = [ - RouteDef(Routes.homeView, page: HomeView), - ]; - @override - Map get pagesMap => _pagesMap; - final _pagesMap = { - HomeView: (data) { - return MaterialPageRoute( - builder: (context) => const HomeView(), - settings: data, - ); - }, - }; -} diff --git a/packages/stacked_localisation/stacked_localisation/example/lib/core/setup_locator.dart b/packages/stacked_localisation/stacked_localisation/example/lib/core/setup_locator.dart deleted file mode 100644 index 5c539976e..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/lib/core/setup_locator.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:get_it/get_it.dart'; -import 'package:stacked_localisation/stacked_localisation.dart'; -import 'package:stacked_services/stacked_services.dart'; - -GetIt locator = GetIt.instance; - -Future setupLocator() async { - var localisationService = await LocalisationService.getInstance(); - locator.registerSingleton(localisationService); - - locator.registerLazySingleton(() => NavigationService()); - locator.registerLazySingleton(() => DialogService()); -} diff --git a/packages/stacked_localisation/stacked_localisation/example/lib/exceptions/exceptions.dart b/packages/stacked_localisation/stacked_localisation/example/lib/exceptions/exceptions.dart deleted file mode 100644 index 7e356f566..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/lib/exceptions/exceptions.dart +++ /dev/null @@ -1 +0,0 @@ -class DuplicateViewKeysException implements Exception {} diff --git a/packages/stacked_localisation/stacked_localisation/example/lib/localisation_string_keys.dart b/packages/stacked_localisation/stacked_localisation/example/lib/localisation_string_keys.dart deleted file mode 100644 index 985e78865..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/lib/localisation_string_keys.dart +++ /dev/null @@ -1,10 +0,0 @@ -/// This code is generated. DO NOT edit by hand - - -class HomeViewStrings { - static String title = 'HomeView.title'; - static String subtitle = 'HomeView.subtitle'; - static String dialogButtonTitle = 'HomeView.dialogButtonTitle'; - static String dialogTitle = 'HomeView.dialogTitle'; - static String dialogDescription = 'HomeView.dialogDescription'; -} diff --git a/packages/stacked_localisation/stacked_localisation/example/lib/main.dart b/packages/stacked_localisation/stacked_localisation/example/lib/main.dart deleted file mode 100644 index 8e2a662cc..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/lib/main.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart' hide Router; -import 'package:stacked_localisation/stacked_localisation.dart'; -import 'package:stacked_services/stacked_services.dart'; - -import 'core/router.gr.dart'; -import 'core/setup_locator.dart'; - -Future main() async { - WidgetsFlutterBinding.ensureInitialized(); - // sets up the internal locator for the localisation service - await setupLocator(); - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData(), - initialRoute: Routes.homeView, - navigatorKey: locator().navigatorKey, - // onGenerateRoute: Router().onGenerateRoute, - ); - } -} diff --git a/packages/stacked_localisation/stacked_localisation/example/lib/services/third_party_module.dart b/packages/stacked_localisation/stacked_localisation/example/lib/services/third_party_module.dart deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/stacked_localisation/stacked_localisation/example/lib/ui/home/home_view.dart b/packages/stacked_localisation/stacked_localisation/example/lib/ui/home/home_view.dart deleted file mode 100644 index 6903160e3..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/lib/ui/home/home_view.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:example/localisation_string_keys.dart'; -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; - -import 'home_viewmodel.dart'; - -class HomeView extends StatelessWidget { - const HomeView({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - builder: (context, model, child) => Scaffold( - appBar: AppBar( - title: Text(model.translate(HomeViewStrings.title)), - ), - body: Center( - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('The text below will be translated'), - Text( - model.translate( - HomeViewStrings.subtitle, - ), - style: TextStyle( - color: Colors.grey[800], - fontWeight: FontWeight.bold, - fontSize: 24, - ), - textAlign: TextAlign.center, - ), - SizedBox(height: 50), - Text('The button below will show a localised dialog'), - OutlineButton( - onPressed: model.handleDialog, - child: Text(model.translate(HomeViewStrings.dialogButtonTitle)), - ) - ], - ), - ), - ), - viewModelBuilder: () => HomeViewModel(), - ); - } -} diff --git a/packages/stacked_localisation/stacked_localisation/example/lib/ui/home/home_viewmodel.dart b/packages/stacked_localisation/stacked_localisation/example/lib/ui/home/home_viewmodel.dart deleted file mode 100644 index 46e0f7b59..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/lib/ui/home/home_viewmodel.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:example/core/setup_locator.dart'; -import 'package:example/localisation_string_keys.dart'; -import 'package:stacked/stacked.dart'; -import 'package:stacked_localisation/stacked_localisation.dart'; -import 'package:stacked_services/stacked_services.dart'; - -class HomeViewModel extends BaseViewModel with LocalisedClass { - final _dialogService = locator(); - - Future handleDialog() async { - await _dialogService.showDialog( - title: translate(HomeViewStrings.dialogTitle), - description: translate(HomeViewStrings.dialogDescription), - ); - } -} diff --git a/packages/stacked_localisation/stacked_localisation/example/pubspec.yaml b/packages/stacked_localisation/stacked_localisation/example/pubspec.yaml deleted file mode 100644 index d5266da23..000000000 --- a/packages/stacked_localisation/stacked_localisation/example/pubspec.yaml +++ /dev/null @@ -1,86 +0,0 @@ -name: example -description: A new Flutter project. - -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. -publish_to: "none" # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+1 - -environment: - sdk: ">=2.7.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.3 - stacked: ^1.7.6 - stacked_services: ^0.5.2 - stacked_localisation: - path: ../ - - get_it: ^4.0.4 - - # navigation - auto_route: ^0.6.7 - -dev_dependencies: - flutter_test: - sdk: flutter - - mockito: - auto_route_generator: - build_runner: - stacked_localisation_generator: - path: ../../stacked_localisation_generator/ - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - assets: - - assets/lang/ - # - images/a_dot_ham.jpeg - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stacked_localisation/stacked_localisation/lib/src/localisation_service.dart b/packages/stacked_localisation/stacked_localisation/lib/src/localisation_service.dart deleted file mode 100644 index ebbb46e4e..000000000 --- a/packages/stacked_localisation/stacked_localisation/lib/src/localisation_service.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stacked_localisation/src/setup_locator.dart'; -import 'package:stacked_localisation/src/utils/locale_provider.dart'; -import 'package:stacked_localisation/src/utils/string_reader.dart'; - -class LocalisationService with WidgetsBindingObserver { - final _localeProvider = locator(); - final _stringReader = locator(); - - static LocalisationService _instance; - - static Future getInstance() async { - if (_instance == null) { - _setupLocator(); - _instance = LocalisationService(); - await _instance.initialise(); - } - return _instance; - } - - LocalisationService() { - WidgetsBinding.instance.addObserver(this); - } - - /// Stores the Strings for the locale that the service was initialised with - Map _localisedStrings; - - String operator [](String key) => _localisedStrings[key]; - - Future initialise() async { - var locale = await _localeProvider.getCurrentLocale(); - _localisedStrings = await _stringReader.getStringsFromAssets(locale); - } - - /// Registers the classes required for the localisation service to work. - /// This function HAS to be called before the application is started. - static void _setupLocator() { - locator.registerLazySingleton(() => LocaleProvider()); - locator.registerLazySingleton(() => StringReader()); - } - - @override - void didChangeLocales(List locale) async { - final currentLocale = locale.first.toString(); - _localisedStrings = await _stringReader.getStringsFromAssets(currentLocale); - } - - // @override - // void didChangeAppLifecycleState(AppLifecycleState state) async { - // // If the user changes language on the device and come back to the app, - // // it will trigger this function with AppLifecycleState.resumed - // if (state == AppLifecycleState.resumed) { - // await _instance.initialise(); - // } - // } -} diff --git a/packages/stacked_localisation/stacked_localisation/lib/src/mixins/localised_class.dart b/packages/stacked_localisation/stacked_localisation/lib/src/mixins/localised_class.dart deleted file mode 100644 index 1045654f0..000000000 --- a/packages/stacked_localisation/stacked_localisation/lib/src/mixins/localised_class.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:stacked_localisation/src/setup_locator.dart'; -import 'package:stacked_localisation/stacked_localisation.dart'; - -mixin LocalisedClass { - final _localisationService = locator(); - String translate(String key, {List replacements}) { - var stringFromFile = _localisationService[key]; - if (replacements != null) { - for (int i = 0; i < replacements.length; i++) { - stringFromFile = - stringFromFile.replaceAll('{$i}', replacements[i].toString()); - } - } - - return stringFromFile; - } -} diff --git a/packages/stacked_localisation/stacked_localisation/lib/src/setup_locator.dart b/packages/stacked_localisation/stacked_localisation/lib/src/setup_locator.dart deleted file mode 100644 index 909f93401..000000000 --- a/packages/stacked_localisation/stacked_localisation/lib/src/setup_locator.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'package:get_it/get_it.dart'; - -final locator = GetIt.instance; diff --git a/packages/stacked_localisation/stacked_localisation/lib/src/utils/locale_provider.dart b/packages/stacked_localisation/stacked_localisation/lib/src/utils/locale_provider.dart deleted file mode 100644 index d7273aea7..000000000 --- a/packages/stacked_localisation/stacked_localisation/lib/src/utils/locale_provider.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:devicelocale/devicelocale.dart'; - -/// Uses the Flutter framework to get the devices selected locale -class LocaleProvider { - Future getCurrentLocale() { - return Devicelocale.currentLocale; - } -} diff --git a/packages/stacked_localisation/stacked_localisation/lib/src/utils/map_helpers.dart b/packages/stacked_localisation/stacked_localisation/lib/src/utils/map_helpers.dart deleted file mode 100644 index ab32b9f27..000000000 --- a/packages/stacked_localisation/stacked_localisation/lib/src/utils/map_helpers.dart +++ /dev/null @@ -1,29 +0,0 @@ -/// Takes in a Map that has 1 level deep nesting and combines the keys of the parent -/// object with the children object. -/// -/// A map as follows: -/// { -/// 'parent' : { -/// 'child': 'value -/// } -/// } -/// -/// will become -/// -/// { -/// 'parent.child': 'value', -/// } -Map flattenMap(Map map) { - Map flatMap = Map(); - var parentKeys = map.keys; - for (var parentKey in parentKeys) { - var childMap = map[parentKey] as Map; - var childKeys = childMap.keys; - for (var childKey in childKeys) { - var flatKey = '$parentKey.$childKey'; - flatMap[flatKey] = map[parentKey][childKey].toString(); - } - } - - return flatMap; -} diff --git a/packages/stacked_localisation/stacked_localisation/lib/src/utils/string_reader.dart b/packages/stacked_localisation/stacked_localisation/lib/src/utils/string_reader.dart deleted file mode 100644 index 2bbef925f..000000000 --- a/packages/stacked_localisation/stacked_localisation/lib/src/utils/string_reader.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/services.dart'; -import 'dart:convert'; - -import 'map_helpers.dart'; - -/// Gets the strings for the locale from the source requested from -class StringReader { - /// Reads the strings from the app bundle in location assets/lang/{locale}.json - /// and returns the json as a String map. - Future> getStringsFromAssets(String locale) async { - // Read the strings from disk - String stringContent; - - try { - stringContent = await rootBundle.loadString('assets/lang/$locale.json'); - } catch (_) { - var majorOnly = locale.split('_').first; - stringContent = - await rootBundle.loadString('assets/lang/$majorOnly.json'); - } - - var flattenedMap = flattenMap(json.decode(stringContent)); - return flattenedMap; - } -} diff --git a/packages/stacked_localisation/stacked_localisation/lib/stacked_localisation.dart b/packages/stacked_localisation/stacked_localisation/lib/stacked_localisation.dart deleted file mode 100644 index cf5d5dc92..000000000 --- a/packages/stacked_localisation/stacked_localisation/lib/stacked_localisation.dart +++ /dev/null @@ -1,4 +0,0 @@ -library stacked_localisation; - -export 'src/localisation_service.dart'; -export 'src/mixins/localised_class.dart'; diff --git a/packages/stacked_localisation/stacked_localisation/pubspec.yaml b/packages/stacked_localisation/stacked_localisation/pubspec.yaml deleted file mode 100644 index 76a017fad..000000000 --- a/packages/stacked_localisation/stacked_localisation/pubspec.yaml +++ /dev/null @@ -1,54 +0,0 @@ -name: stacked_localisation -description: A service class that helps with implementing localisation functionality in your application -version: 0.2.2 -homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_localisation - -environment: - sdk: ">=2.7.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - - devicelocale: ^0.3.1 - get_it: ^4.0.4 - -dev_dependencies: - flutter_test: - sdk: flutter - mockito: - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stacked_localisation/stacked_localisation/test/localisation_service_test.dart b/packages/stacked_localisation/stacked_localisation/test/localisation_service_test.dart deleted file mode 100644 index 57e7e3bd5..000000000 --- a/packages/stacked_localisation/stacked_localisation/test/localisation_service_test.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; -import 'package:stacked_localisation/src/localisation_service.dart'; - -import 'test_helpers.dart'; - -void main() { - group('LocalisationServiceTest -', () { - setUp(() => registerServices()); - tearDown(() => unregisterServices()); - - group('initialise -', () { - test('When called should get the locale from the locale provider', - () async { - final localeProvider = getAndRegisterLocaleProviderMock(); - var service = LocalisationService(); - await service.initialise(); - verify(localeProvider.getCurrentLocale()); - }); - - test( - 'When called should request the strings using that returned locale from the provider', - () async { - final stringReader = getAndRegisterStringReaderMock(); - var service = LocalisationService(); - await service.initialise(); - verify(stringReader.getStringsFromAssets('en')); - }); - }); - }); -} diff --git a/packages/stacked_localisation/stacked_localisation/test/localised_class_test.dart b/packages/stacked_localisation/stacked_localisation/test/localised_class_test.dart deleted file mode 100644 index 0291a4da8..000000000 --- a/packages/stacked_localisation/stacked_localisation/test/localised_class_test.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; -import 'package:stacked_localisation/src/localisation_service.dart'; -import 'package:stacked_localisation/src/mixins/localised_class.dart'; - -import 'test_helpers.dart'; - -class LocalisationModel with LocalisedClass {} - -void main() { - group('LocalisedClassTest -', () { - setUp(() => registerServices()); - tearDown(() => unregisterServices()); - - group('translate -', () { - test( - 'When called should return the value returned from the localisation service', - () { - var model = LocalisationModel(); - var translated = model.translate('key'); - expect(translated, 'hello, world'); - }); - - test( - 'When called with a list of replacements should replace the value in order', - () { - getAndRegisterLocalistionService( - localisationString: 'You have tapped {0} times'); - var model = LocalisationModel(); - var translated = model.translate('key', replacements: [9]); - expect(translated, 'You have tapped 9 times'); - }); - - test( - 'When called with multiple replacements for {0} should replace all of them', - () { - getAndRegisterLocalistionService( - localisationString: 'The number {0} will be shown here {0} too.'); - var model = LocalisationModel(); - var translated = model.translate('key', replacements: [9]); - expect(translated, 'The number 9 will be shown here 9 too.'); - }); - - test( - 'When called with multiple replacements for different indices should replace all of them', - () { - getAndRegisterLocalistionService(localisationString: '{0} {1} {2} {3}'); - var model = LocalisationModel(); - var translated = - model.translate('key', replacements: [9, 5, 'String', true]); - expect(translated, '9 5 String true'); - }); - }); - }); -} diff --git a/packages/stacked_localisation/stacked_localisation/test/map_helpers_test.dart b/packages/stacked_localisation/stacked_localisation/test/map_helpers_test.dart deleted file mode 100644 index 700e253e6..000000000 --- a/packages/stacked_localisation/stacked_localisation/test/map_helpers_test.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:stacked_localisation/src/utils/map_helpers.dart'; - -void main() { - group('MapHelpersTest -', () { - group('flattenMap -', () { - test( - 'Given map with 1 level deep map nesting, should combine key for that map', - () { - final parentKey = 'Home'; - final childKey = 'title'; - Map oneLevelDeepMap = { - parentKey: {childKey: 'This is a title'} - }; - - var flattenedMap = flattenMap(oneLevelDeepMap); - expect(flattenedMap.containsKey('$parentKey.$childKey'), true); - }); - - test( - 'Given map with 1 level deep map nesting, should store "This is a title" in Home.title key', - () { - final parentKey = 'Home'; - final childKey = 'title'; - final childValue = 'This is a title'; - Map oneLevelDeepMap = { - parentKey: {childKey: childValue} - }; - - var flattenedMap = flattenMap(oneLevelDeepMap); - expect(flattenedMap['$parentKey.$childKey'], childValue); - }); - - test( - 'Given map with root child that contains a map with 2 children should have 2 keys in flattened map', - () { - Map oneLevelDeepMap = { - 'Home': { - 'title': 'This is a title', - 'subtitle': 'subtitles are for losers' - } - }; - - var flattenedMap = flattenMap(oneLevelDeepMap); - expect(flattenedMap.keys.length, 2); - }); - }); - }); -} diff --git a/packages/stacked_localisation/stacked_localisation/test/test_helpers.dart b/packages/stacked_localisation/stacked_localisation/test/test_helpers.dart deleted file mode 100644 index 9d89aee42..000000000 --- a/packages/stacked_localisation/stacked_localisation/test/test_helpers.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:mockito/mockito.dart'; -import 'package:stacked_localisation/src/localisation_service.dart'; -import 'package:stacked_localisation/src/setup_locator.dart'; -import 'package:stacked_localisation/src/utils/locale_provider.dart'; -import 'package:stacked_localisation/src/utils/string_reader.dart'; - -class LocaleProviderMock extends Mock implements LocaleProvider {} - -class StringReaderMock extends Mock implements StringReader {} - -class LocalisationServiceMock extends Mock implements LocalisationService {} - -LocalisationService getAndRegisterLocalistionService( - {String localisationString = 'hello, world'}) { - _removeRegistrationIfExists(); - var mock = LocalisationServiceMock(); - when(mock[any]).thenReturn(localisationString); - locator.registerSingleton(mock); - return mock; -} - -LocaleProvider getAndRegisterLocaleProviderMock({String locale = 'en'}) { - _removeRegistrationIfExists(); - var mock = LocaleProviderMock(); - when(mock.getCurrentLocale()) - .thenAnswer((realInvocation) => Future.value(locale)); - locator.registerSingleton(mock); - return mock; -} - -StringReader getAndRegisterStringReaderMock() { - _removeRegistrationIfExists(); - var mock = StringReaderMock(); - locator.registerSingleton(mock); - return mock; -} - -// Call this before any service registration helper. This is to ensure that if there -// is a service registered we remove it first. We register all services to remove boiler plate from tests -void _removeRegistrationIfExists() { - if (locator.isRegistered()) { - locator.unregister(); - } -} - -void registerServices() { - getAndRegisterLocaleProviderMock(); - getAndRegisterStringReaderMock(); - getAndRegisterLocalistionService(); -} - -void unregisterServices() { - locator.unregister(); - locator.unregister(); - locator.unregister(); -} diff --git a/packages/stacked_localisation/stacked_localisation_generator/.gitignore b/packages/stacked_localisation/stacked_localisation_generator/.gitignore deleted file mode 100644 index bb431f0d5..000000000 --- a/packages/stacked_localisation/stacked_localisation_generator/.gitignore +++ /dev/null @@ -1,75 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Flutter.podspec -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/Flutter/flutter_export_environment.sh -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/stacked_localisation/stacked_localisation_generator/.metadata b/packages/stacked_localisation/stacked_localisation_generator/.metadata deleted file mode 100644 index eeda8507b..000000000 --- a/packages/stacked_localisation/stacked_localisation_generator/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 8af6b2f038c1172e61d418869363a28dffec3cb4 - channel: stable - -project_type: package diff --git a/packages/stacked_localisation/stacked_localisation_generator/CHANGELOG.md b/packages/stacked_localisation/stacked_localisation_generator/CHANGELOG.md deleted file mode 100644 index 1dda08019..000000000 --- a/packages/stacked_localisation/stacked_localisation_generator/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -## 0.1.0+1 - -- Changes path package dependency from 1.7.0 -> 1.6.4 because flutter_test package to fix weird error - -## 0.1.0 - -- Generates the string keys based on the language files in assets/lang diff --git a/packages/stacked_localisation/stacked_localisation_generator/LICENSE b/packages/stacked_localisation/stacked_localisation_generator/LICENSE deleted file mode 100644 index 029d251d5..000000000 --- a/packages/stacked_localisation/stacked_localisation_generator/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Dane Mackier and other contributors - -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: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -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. \ No newline at end of file diff --git a/packages/stacked_localisation/stacked_localisation_generator/README.md b/packages/stacked_localisation/stacked_localisation_generator/README.md deleted file mode 100644 index e196119fd..000000000 --- a/packages/stacked_localisation/stacked_localisation_generator/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Stacked Localisation Generator - -A code generator that generates all the keys in a language file to be used with the [stacked_localisation](https://github.com/FilledStacks/stacked/tree/master/packages/stacked_localisation/stacked_localisation) package. - -## Setup - -This package (and build_runner if you don't already have it) should be added as a dev dependency in the project you're using. - -```yaml -dev_dependencies: - ... - build_runner: - stacked_localisation_generator: -``` - -Then create a new folder in root called assets with another folder inside it called lang. This folder will contain the language json or yaml files. Here's an example of a file called en.json - -```json -{ - "HomeView": { - "title": "This is my Home", - "subtitle": "I live in this Home" - } -} -``` - -or for en.yaml - -```yaml -HomeView: - title: This is my Home - subtitle: I live in this Home -``` - -When you run `flutter pub run build_runner build --delete-conflicting-outputs` the package will generate a new file called `localisation_string_keys.dart` that can be found at the root of the lib folder that contains type save keys for the language string definition above. The above will produce the following keys. - -```dart -/// This code is generated. DO NOT edit by hand - -class HomeViewStrings { - static String title = 'HomeView.title'; - static String subtitle = 'HomeView.subtitle'; -} -``` - -This can be accessed statically throughout the application where the keys are required. To see a full example of using stacked_localisaion you can check out the walkthrough [here](https://github.com/FilledStacks/stacked/tree/master/packages/stacked_localisation/stacked_localisation). - -## Contributing - -To run all generator tests: - -```sh -flutter pub run build_runner test -``` diff --git a/packages/stacked_localisation/stacked_localisation_generator/build.yaml b/packages/stacked_localisation/stacked_localisation_generator/build.yaml deleted file mode 100644 index 315772f14..000000000 --- a/packages/stacked_localisation/stacked_localisation_generator/build.yaml +++ /dev/null @@ -1,7 +0,0 @@ -builders: - localisation_string_key_generator: - import: "package:stacked_localisation_generator/stacked_localisation_generator.dart" - builder_factories: ["localisationStringKeyGenerator"] - build_extensions: { "$lib$": ["all_files.txt"] } - auto_apply: dependents - build_to: source diff --git a/packages/stacked_localisation/stacked_localisation_generator/lib/src/localisation_string_key_generator.dart b/packages/stacked_localisation/stacked_localisation_generator/lib/src/localisation_string_key_generator.dart deleted file mode 100644 index 1371348e3..000000000 --- a/packages/stacked_localisation/stacked_localisation_generator/lib/src/localisation_string_key_generator.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:path/path.dart' as p; -import 'package:build/build.dart'; -import 'package:glob/glob.dart'; -import 'package:yaml/yaml.dart'; - -import 'map_utils.dart'; - -class LocalisationStringKeyGenerator implements Builder { - static final _allLanguageFiles = new Glob('assets/lang/*'); - - static AssetId _stringKeysOutput(BuildStep buildStep) { - return AssetId( - buildStep.inputId.package, - p.join('lib', 'localisation_string_keys.dart'), - ); - } - - @override - FutureOr build(BuildStep buildStep) async { - await for (final input in buildStep.findAssets(_allLanguageFiles)) { - var fileContent = await buildStep.readAsString(input); - - var languageStringsMap = loadYaml(fileContent) as Map; - - var localisationstrings = getStringKeysCodeFromMap(languageStringsMap); - - // Bail out after the first file. We only need 1 language file to define all the keys since - // it's expected that the language files will all contain the same keys. - final outputFile = _stringKeysOutput(buildStep); - return buildStep.writeAsString(outputFile, localisationstrings); - - // TODO: Throw an exception when the language files have different keys from one another - } - } - - @override - Map> get buildExtensions { - return const { - r'$lib$': const ['localisation_string_keys.dart'], - }; - } -} diff --git a/packages/stacked_localisation/stacked_localisation_generator/lib/src/map_utils.dart b/packages/stacked_localisation/stacked_localisation_generator/lib/src/map_utils.dart deleted file mode 100644 index 8168609f8..000000000 --- a/packages/stacked_localisation/stacked_localisation_generator/lib/src/map_utils.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:mustache/mustache.dart'; - -const String _LocalisationStringsClassDefinition = ''' - class LocalisationStrings { - - } - '''; - -const String GeneratorComment = ''' -/// This code is generated. DO NOT edit by hand -'''; - -const String _MustacheTemplate = ''' -{{{generatorComment}}} - -{{#stringClasses}} -class {{className}} { - {{#keys}} - static String {{propertyName}} = '{{propertyValue}}'; - {{/keys}} -} -{{/stringClasses}} -'''; - -String getStringKeysCodeFromMap(Map languageMap) { - var template = Template(_MustacheTemplate); - var classInfos = List(); - for (var viewKey in languageMap.keys) { - var classMap = languageMap[viewKey] as Map; - var classProperties = classMap.keys - .map((propertyKey) => PropertyInfo( - propertyName: propertyKey, propertyValue: '$viewKey.$propertyKey')) - .toList(); - var info = - StringsClassInfo(className: viewKey + 'Strings', keys: classProperties); - classInfos.add(info); - } - - var output = template.renderString({ - 'generatorComment': GeneratorComment, - 'stringClasses': classInfos, - }); - return output; -} - -class StringsClassInfo { - final String className; - final List keys; - - StringsClassInfo({this.className, this.keys}); -} - -class PropertyInfo { - final String propertyName; - final String propertyValue; - - PropertyInfo({this.propertyName, this.propertyValue}); -} diff --git a/packages/stacked_localisation/stacked_localisation_generator/lib/stacked_localisation_generator.dart b/packages/stacked_localisation/stacked_localisation_generator/lib/stacked_localisation_generator.dart deleted file mode 100644 index 92c7d1978..000000000 --- a/packages/stacked_localisation/stacked_localisation_generator/lib/stacked_localisation_generator.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:build/build.dart'; -import 'package:stacked_localisation_generator/src/localisation_string_key_generator.dart'; - -Builder localisationStringKeyGenerator(BuilderOptions options) => - LocalisationStringKeyGenerator(); diff --git a/packages/stacked_localisation/stacked_localisation_generator/pubspec.yaml b/packages/stacked_localisation/stacked_localisation_generator/pubspec.yaml deleted file mode 100644 index 3e86acd55..000000000 --- a/packages/stacked_localisation/stacked_localisation_generator/pubspec.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: stacked_localisation_generator -description: a package that generates string keys for use with the the localisation service in a type safe manner -version: 0.1.0+1 -homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_localisation/stacked_localisation_generator - -environment: - sdk: ">=2.7.0 <3.0.0" - -dependencies: - build: ^1.3.0 - source_gen: ^0.9.6 - analyzer: ^0.39.17 - mustache: ^1.1.1 - glob: ^1.2.0 - path: ^1.6.4 - yaml: ^2.2.1 - -dev_dependencies: - test: - build_runner: - build_test: diff --git a/packages/stacked_localisation/stacked_localisation_generator/test/localisation_string_key_generator_test.dart b/packages/stacked_localisation/stacked_localisation_generator/test/localisation_string_key_generator_test.dart deleted file mode 100644 index 2c1bce295..000000000 --- a/packages/stacked_localisation/stacked_localisation_generator/test/localisation_string_key_generator_test.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:build_test/build_test.dart'; -import 'package:stacked_localisation_generator/src/localisation_string_key_generator.dart'; -import 'package:test/test.dart'; - -void main() { - group('LocalisationStringKeyGeneratorTest -', () { - group('Assets -', () { - test( - 'When given a JSON file, should generate a class called HomeViewStrings', - () async { - await testBuilder(LocalisationStringKeyGenerator(), { - 'a|assets/lang/en.json': r''' -{ - "HomeView": { - "title": "This is my Home", - "subtitle": "I live in this Home" - } -}''' - }, outputs: { - 'a|lib/localisation_string_keys.dart': r''' -/// This code is generated. DO NOT edit by hand - - -class HomeViewStrings { - static String title = 'HomeView.title'; - static String subtitle = 'HomeView.subtitle'; -} -''' - }); - }); - - test( - 'When given a YAML file, should generate a class called HomeViewStrings', - () async { - await testBuilder(LocalisationStringKeyGenerator(), { - 'a|assets/lang/en.yaml': r''' -HomeView: - title: This is my Home - subtitle: I live in this Home -''' - }, outputs: { - 'a|lib/localisation_string_keys.dart': r''' -/// This code is generated. DO NOT edit by hand - - -class HomeViewStrings { - static String title = 'HomeView.title'; - static String subtitle = 'HomeView.subtitle'; -} -''' - }); - }); - }); - }); -} diff --git a/packages/stacked_localisation/stacked_localisation_generator/test/map_utils_test.dart b/packages/stacked_localisation/stacked_localisation_generator/test/map_utils_test.dart deleted file mode 100644 index 462a616f7..000000000 --- a/packages/stacked_localisation/stacked_localisation_generator/test/map_utils_test.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:stacked_localisation_generator/src/map_utils.dart'; -import 'package:test/test.dart'; - -void main() { - group('MapUtilsTest -', () { - group('getStringKeysCodeFromMap -', () { - test('When given an empty map should return generator comment only', () { - Map testmap = {}; - var stringsCode = getStringKeysCodeFromMap(testmap); - expect(stringsCode, ''' -$GeneratorComment - -'''); - }); - - test( - 'When given map with key HomeView, should generate a class called HomeViewStrings', - () { - Map testmap = {'HomeView': {}}; - var stringsCode = getStringKeysCodeFromMap(testmap); - expect(stringsCode, ''' -$GeneratorComment - -class HomeViewStrings { -} -'''); - }); - - test( - 'When given map with key HomeView and internal keys, should generate a class called HomeViewStrings with properties matching keys from HomeView map', - () { - Map testmap = { - 'HomeView': { - 'title': 'This is my title', - 'subtitle': 'This is a subtitle', - } - }; - var stringsCode = getStringKeysCodeFromMap(testmap); - expect(stringsCode, ''' -$GeneratorComment - -class HomeViewStrings { - static String title = 'HomeView.title'; - static String subtitle = 'HomeView.subtitle'; -} -'''); - }); - }); - }); -} diff --git a/packages/stacked_services/.gitignore b/packages/stacked_services/.gitignore deleted file mode 100644 index 5509f6e7b..000000000 --- a/packages/stacked_services/.gitignore +++ /dev/null @@ -1,76 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Flutter.podspec -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/Flutter/flutter_export_environment.sh -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages -*.lock diff --git a/packages/stacked_services/.metadata b/packages/stacked_services/.metadata deleted file mode 100644 index 3b511f7d5..000000000 --- a/packages/stacked_services/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 - channel: stable - -project_type: package diff --git a/packages/stacked_services/CHANGELOG.md b/packages/stacked_services/CHANGELOG.md deleted file mode 100644 index 4843865c5..000000000 --- a/packages/stacked_services/CHANGELOG.md +++ /dev/null @@ -1,353 +0,0 @@ -## 0.8.17 - -- Add `buttonTitleColor` and `cancelTitleColor` to showDialog method when a dialogPlatform is passed. - -## 0.8.16 - -- Updated `get` to latest package version -- Fixed bug on unknown `isSnackbar` and `SnackDismissDirection` - -## 0.8.15 - -### Bottom Sheet Unique Name -When showing a bottom sheet we'll now give it a uique route name based on the properties passed in. The format of this will differ between the general and custom. -- general: `general_{md5hashOfTitleAndDescription} -- custom: `variant_{md5HashOfTitleDescriptionMainButtonTitleSecondaryButtonTitle} - -The expected output of a bottom sheet will look like this - -```dart -general_82fda55933c6a64eb961dfb6e13bdd4f // General and confirmation bottom sheet -BottomSheetType.FloatingBox_33557de8784ebb01ebd64b71926b5933 // Custom Bottom sheet with variant -``` - -## 0.8.14 - -- Updated `get` to latest package version - -## 0.8.13 - -- Updated `get` to latest package version -- Fixed bug on unknown `Get.reference` and `route.name` - -## 0.8.12 - -- Remove useRootNavigator from Dialog Service - -## 0.8.11 - -- Add `ignoreSafeArea` parameter to `showBottomSheet` and `showCustomSheet`, pass down to `get` bottomSheet method - -## 0.8.10 - -- Pass down generic types to `NavigationService` methods along with the properties relevant to `get` router - -## 0.8.9 - -- Exports `get` package - -## 0.8.8 - -- Added generic type arguments for `OverlayResponse` and `OverlayRequest` for dialogs and bottom sheets -- Deprecated `responseData` and added new property `data` that corresponds to that type provided -- Added 2 generic type arguments for `showCustomDialog` and `showCustomSheet` -- The first generic type argument for `showCustomDialog` from `DialogService` is intended for the response `data` and the second generic type argument for `showCustomSheet` from `BottomSheetService` is intended for the request payload `data` - -## 0.8.7 - -- Added `currentArguments` in NavigationService - -## 0.8.6 - -- Fixes empty space on showSnackbar if title is not given - -## 0.8.5 - -- Adds prevent duplicates and parameters to `NavigationService` - -## 0.8.4 - -- Adds support for TestSweets automation by providing keys - -## 0.8.3 - -- Fixed where currentRoute in NavigationService was always null `routeObserver` from `StackedServices` needs to passed on `navigatorObservers` in `MaterialApp` - -## 0.8.2 - -- Fixed barrierDismissible issue on BottomSheetService - -## 0.8.1 - -Updates to latest version of get - -## 0.8.0-nullsafety.1 - -- Migrates to null safety - -## 0.7.3 - -- Removes the warning from the get package -- Complies with "Replaced reference to obsolete FlatButton button class in SnackBar" -- Support `ChangeNotifier` in `ReactiveServiceMixin` - -## 0.7.2 - -- Fixes a regression bug for allowing null title and content for a dialog - -## 0.7.1+3 - -- Fixes onTap signature - -## 0.7.1+2 - -- Fixes snackBar config null exception when using builder - -## 0.7.1+1 - -- Removes requirement for config - -## 0.7.1 - -- Adds `snackbarConfigBuilder` to allow for building the config at runtime instead of before `runApp` is called - -## 0.7.0 - -- Adds custom mainButtonBuilder to allow you to build a main button UI from the variant passed in - -## 0.6.8 - -- Adds isOpen functionality - -## 0.6.7 - -- Made dialog title or content optional - -## 0.6.6 - -- Adds title and message textAlign to the config - -## 0.6.5 - -- Allows for null title and message for custom snackbar - -## 0.6.4 - -- Adds `isScrollControlled` to the bottom sheet function - -## 0.6.3 - -- Adds `Color` to dialog service to set the color of the basic dialogs - -## 0.6.2 - -- Adds functionality to allow to set isDismissible on the `BottomSheetService` - -## 0.6.1 - -- Introduces the new `StackedService` class with the navigation properties on it. This is introduced to remove the confusion around every service having a key to set. So going forward if you want to use any of the services you just set the navigatorKey on the `StackedService`. - -## 0.6.0+1 - -- Adds typed return for dialogs and sheets - -## 0.6.0 - -- Adds bottom sheet service -- Adds dark theme for basic Material Dialogs -- Updates the way we complete Dialogs - -## 0.5.4+5 - -- Updates get version to latest - -## 0.5.4+4 - -- Adds an id to the `back` function on the `NavigationService` for nested back calls. - -## 0.5.4+3 - -- Adds type to the navigation key - -## 0.5.4+2 - -- Adds barrier label to custom dialog - -## 0.5.4+1 - -- Updated get version - -## 0.5.4 - -- Exposes `currentRoute` in the `NavigationService` - -## 0.5.3 - -- Exposes `previousRoute` in the `NavigationService` - -## 0.5.2 - -- Changes Dialog response to a `dynamic` value instead of `List` - -## 0.5.1 - -### Custom Dialog - -- Add ability, like Custom Snackbar, to registry multiple variants of dialog. -- Use variant term instead customData, I thins this is more clear then customData term to refer a specific type of Dialog -- Add a builder property to declare the UI. -- Rename registerCustomDialogUi to registerCustomDialogBuilder. -- Update documentation. - -### Custom Snackbar - -- Use variant term instead customData, like I said for Custom Dialog, I think this is more clear use the variant term instead customData to refer a specific. -- Normalize the name, instead registerCustomSnackbarconfig use the registerCustomSnackbarConfig. -- Update documentation. - -## 0.5.0 - Custom Snackbar styling - -- Adds `SnackbarConfig` functionality and associated functions for custom snackbar styling - -## 0.4.11 - -- Exposes `barrierDismssible` on showConfirmation dialog - -## 0.4.10 - -- Adds null check to dialog service completer - -## 0.4.9 - -- Completes dialogCompleter for confirmation dialog as well - -## 0.4.8 - -- Completes dialogCompleter if the custom dialog is dismissed without the button tap - -## 0.4.7 - -- Makes the dialog completer finish when dialog is dismissed using barrier dismissal - -## 0.4.6 - -- Removed injectable - -## 0.4.5 - -- Bumps injectable version - -## 0.4.4+3 - -- Bumps get version - -## 0.4.4+2 - -- Readme updates to include services in - -## 0.4.4+1 - -- Adds id's to all navigation functions to reference correct nav key - -## 0.4.4 - -- Exposes functionality for nested navigator keys - -## 0.4.3 - -- Exposes barrierColor on the dialog service - -## 0.4.2 - -- Adds barrierDismissable optional boolean -- Adds instantInit option to allow to show snackbar in initState function call - -## 0.4.1 - -- Adds custom data parameter to dialog service -- Adds default values for show custom dialog - -## 0.4.0 - -- Adds custom UI option to the dialog builder - -## 0.3.4 - -- Added arguments into the navigation service for clearStackAndshow and others - -## 0.3.3 - -- DialogService now uses DialogTheme of the context instead of TextTheme - -## 0.3.2+1 - -- Makes use of offAllNamed from get for clear backstack functionality - -## 0.3.2 - -- Fixed naming changes to be consistent with the function name - -## 0.3.1+2 - -- When clearing stacking and showing we push instead of replace - -## 0.3.1+1 - -- Provided route as the predicate for the "till" functions - -## 0.3.1 - -- Expose get key through all services in case they are used on their own without navigation service - -## 0.3.0+1 - -- Injectable version update - -## 0.3.0 - -- Dialog Service now shows platform specific dialogs -- Get version bumped -- Services is now Flutter web compatible - -## 0.2.4 - -- Fixed a null exception caused by 2.5.0 of get - -## 0.2.3 - -- Exposed the navigate with transition and replace with transition functionalities -- Added `NavigationTransition` class to give transitions type safety and avoid using Get's enum value -- Added a config function to set the default navigation behaviour - -## 0.2.2+1 - -- Updated get package version - -## 0.2.2 - -- Adds parameter for pop result to NavigationService - -## 0.2.1+1 - -- Changelog style updates - -## 0.2.1 - -- Moved private files into src folder as per package conventions - -## 0.2.0+2 - -- Fixed a function name type in Navigation Service - -## 0.2.0+1 - -- Updates webpage for package - -## 0.2.0 - -- Expanded the Navigation Service functionality to expose more functionalities - -## 0.1.0 - -- Initial commit with basic services -- Adds the `DialogService`, `NavigationService` and `SnackBarService` diff --git a/packages/stacked_services/LICENSE b/packages/stacked_services/LICENSE deleted file mode 100644 index 9c50c4709..000000000 --- a/packages/stacked_services/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Dane Mackier and other contributors - -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: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -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. diff --git a/packages/stacked_services/README.md b/packages/stacked_services/README.md deleted file mode 100644 index 34438e329..000000000 --- a/packages/stacked_services/README.md +++ /dev/null @@ -1,501 +0,0 @@ -# Stacked Services - -Provides some essential services to aid in implementing the Stacked architecture. These services are only here to reduce boilerplate code for the users of the Stacked Architecture that uses the architecture as instructed by FilledStacks on the architecture series. - -## Migration from 0.5.x -> 0.6.x - -- The custom builder function has changed for the `DialogService` instead of using `registerCustomDialogBuilder` you should now create a map of builders and pass it to `registerCustomDialogBuilders`. -- If you're still using `registerCustomDialogBuilder` the builder function now takes a third argument of type `Function(DialogResponse)` that you can call completer. - -Old way: - -```dart -service.registerCustomDialogBuilder(variant: Dialog.basic, builder: (context, request) => Dialog(...)) -``` - -New way: - -```dart -service.registerCustomDialogBuilder(variant: Dialog.basic, builder: (context, request, completer) => Dialog(...)) -``` - -Take note of the third parameter in the builder function that you can call to complete the builder instead of using the dialog service directly. You can simply call - -```dart -completer(DialogResponse(...)); -``` - -## Services - -The following services are included in the package - -- **NavigationService:** Makes use of the [Get](https://pub.dev/packages/get) package to expose basic navigation functionalities -- **DialogService**: Makes use of the [Get](https://pub.dev/packages/get) package to expose functionality that allows the dev to show dialogs from the ViewModels -- **SnackbarService**: Makes use of the [Get](https://pub.dev/packages/get) to expose the snack bar functionality to devs. -- **BottomSheetService**: Makes use of the [Get](https://pub.dev/packages/get) to expose the bottom sheet functionality. - -The services can be registered with get_it normally as you would usually - -```dart -final locator = GetIt.instance; - -locator.registerLazySingleton(() => NavigationService()); -``` - -If you're using Injectable as recommended you can register the services using a third party services module. Create a new file in your services folder called thirdparty_services_module.dart. - -```dart -@module -abstract class ThirdPartyServicesModule { - @lazySingleton - NavigationService get navigationService; - @lazySingleton - DialogService get dialogService; - @lazySingleton - SnackbarService get snackBarService; - @lazySingleton - BottomSheetService get bottomSheetService; -} -``` - -If you now run - -``` -flutter pub run build_runner build -``` - -Your services will be available as usual on your locator instance. - -## Usage - -To use ANY OF the services you have to assign the navigation key to your Flutter application. - -```dart -MaterialApp( - title: 'Stacked Services', - navigatorKey: StackedService.navigatorKey, - // home: AddCardView(), // Used when testing a view - initialRoute: Routes.startupViewRoute, - onGenerateRoute: Router().onGenerateRoute, -); -``` - -If you're only using the `DialogService` it also exposes the navigation key. **You only have to set the key for one of the services and it'll work for all the other services.** If you set the nav key using the navigation service you don't have to set it for the DialogService and vice versa. - -## Dialog Service - -The `DialogService` will show a platform-specific dialog by default. You can change this by passing in `dialogPlatform` to your show dialog call. - -```dart -await _dialogService.showDialog( - title: 'Test Dialog Title', - description: 'Test Dialog Description', - dialogPlatform: DialogPlatform.Cupertino, -); -``` - -### Custom Dialog UI - -In addition to platform-specific UI, you can also build a custom dialog. To do that we'll do the following. Start by creating an enum called `DialogType`. - -```dart -/// The type of dialog to show -enum DialogType { basic, form } -``` - -In your UI folder or shared folder under UI, if you have one, create a new file called `setup_dialog_ui.dart`. Inside you will create a new function called `setupDialogUi`. In there you will create a Map of builders that will map to the `enum` values you created above. To keep code maintenance to the highest level you should make each of the widgets into its own widget and construct that instead of building the UI inline. - -```dart -void setupDialogUi() { - final dialogService = locator(); - - final builders = { - DialogType.basic: (context, sheetRequest, completer) => - _BasicDialog(request: sheetRequest, completer: completer), - DialogType.form: (context, sheetRequest, completer) => - _FormDialog(request: sheetRequest, completer: completer), - }; - - dialogService.registerCustomDialogBuilders(builders); -} - -class _BasicDialog extends StatelessWidget { - final DialogRequest request; - final Function(DialogResponse) completer; - const _BasicDialog({Key key, this.request, this.completer}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Dialog( - child: /* Build your dialog UI here */ - ); - } -} - -class _FormDialog extends StatelessWidget { - final DialogRequest request; - final Function(DialogResponse) completer; - const _FormDialog({Key key, this.request, this.completer}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Dialog( - child: /* Build your dialog UI here */ - ); - } -} - -``` - -The `DialogRequest` has a few properties that can make you easily decide which widgets to place in the dialog to show. All these properties can be passed directly to the `showCustomDialog` function. Here are all the properties available for you to use. - -```dart -/// The title for the dialog -final String title; - -/// Text so show in the dialog body -final String description; - -/// Indicates if an image should be used or not -final bool hasImage; - -/// The URL / path to the image to show -final String imageUrl; - -/// The text shown in the main button -final String mainButtonTitle; - -/// A bool to indicate if you should show an icon in the main button -final bool showIconInMainButton; - -/// The text to show on the secondary button on the dialog (cancel usually) -final String secondaryButtonTitle; - -/// Indicates if you should show an icon in the main button -final bool showIconInSecondaryButton; - -/// The text shown on the third button on the dialog -final String additionalButtonTitle; - -/// Indicates if you should show an icon in the additional button -final bool showIconInAdditionalButton; - -/// Indicates if the dialog takes input -final bool takesInput; - -/// Intended to be used with enums. If you want to create multiple different -/// dialogs. Pass your enum in here and check the value in the builder -final dynamic variant; - -/// Extra data to be passed to the UI -final dynamic customData; -``` - -### Setup and usage - -After you have created your register function go to your main.dart file and after you've registered your services with the locator call `setupDialogUi`. - -```dart -void main() { - setupLocator(); - setupDialogUi(); - runApp(MyApp()); -} -``` - -Now in your ViewModels, you can make use of the dialog as follows. - -```dart -await _dialogService.showCustomDialog( - variant: DialogType.base, // Which builder you'd like to call that was assigned in the builders function above. - title: 'This is a custom UI with Text as main button', - description: 'Sheck out the builder in the dialog_ui_register.dart file', - mainButtonTitle: 'Ok', -); -``` - -### Returning Data from Custom Dialog - -The builder function supplied for a Custom dialog builder has a parameter of type `Function(DialogResponse)` as the last parameter. Calling the completer function and passing in a `DialogResponse` object will return it to the caller that's awaiting on the dialog response UI. So when you have a tap handler in your dialog and you want to close the dialog, use the `completer(DialogResponse())` function. - -```dart -var response = await _dialogService.showCustomDialog( - variant: DialogType.form, - title: 'My custom dialog', - description: 'This is my dialog description', - mainButtonTitle: 'Confirm', -); - -if(response.confirmed) { - // Do some confirmation action here. -} -``` - -## Navigation Service - -The `NavigationService` will allow you to navigate your app easily from the `ViewModel`. No need for `BuildContext`. - -- **NOTE: The table below expects you to have followed above steps, and intialized `NavigationService` like this: `final NavigationService _navigationService = locator();`** -- The table below shows each function you can use with its return type and description: - -| Function | Return Type | Description | -| --------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------- | -| `config` | `void` | Allows you to configure the default behaviour for navigation. | -| `navigateWithTransition` | `Future` | Pushes `page` onto the navigation stack. This uses the `page` itself `Widget` instead of routeName `String` | -| `replaceWithTransition` | `Future` | Replaces current view in the navigation stack. This uses the `page` itself `Widget` instead of routeName `String` | -| `back` | `bool` | Pops the current scope and indicates if you can pop again | -| `popUntil` | `void` | Pops the back stack until the predicate is satisfied | -| `popRepeated` | `void` | Pops the back stack the number of times you indicate with `popTimes` | -| `navigateTo` | `Future` | Pushes `routeName` onto the navigation stack | -| `navigateToView` | `Future` | Pushes `view` onto the navigation stack | -| `replaceWith` | `Future` | Replaces the current route with the `routeName` | -| `clearStackAndShow` | `Future` | Clears the entire back stack and shows `routeName` | -| `clearTillFirstAndShow` | `Future` | Pops the navigation stack until there's 1 view left then pushes `routeName` onto the stack | -| `clearTillFirstAndShowView` | `Future` | Pops the navigation stack until there's 1 view left then pushes `view` onto the stack | -| `pushNamedAndRemoveUntil` | `Future` | Push route and clear stack until predicate is satisfied | -| | - -## Route observation - -If you want the current route to be set during navigations then you have to add a route observer. - -```dart -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Stacked Services Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - navigatorObservers: [StackedService.routeObserver], ///<- Here - navigatorKey: StackedService.navigatorKey, - initialRoute: auto_router.Routes.homeScreenRoute, - onGenerateRoute: auto_router.Router().onGenerateRoute, - ); - } -} -``` - -## Snackbar Service - -The `SnackbarService` allows you to show a snack bar from the `ViewModel`. Logic and state is handled in the `ViewModel` this is where you know something went wrong, a results is unexpected or when a user has completed an action. Instead of routing the action back to the UI to show a snackbar using the context we can show it directly from the ViewModel using the `SnackbarService`. - -### Basic Usage - -To use the service is quite easy. Here is an example of how you'd show a snackbar. - -```dart -_snackbarService.showSnackbar( - message: 'This is a snack bar', - title: 'The title', - duration: Duration(seconds: 2), - onTap: (_) { - print('snackbar tapped'); - }, - mainButtonTitle: 'Undo', - onMainButtonTapped: () => print('Undo the action!'), -); -``` - -This will show a default Snackbar styled with the background set as Dark grey and the text as white. There will be a main button on the right that will fire the function and print 'Undo the action!' when it's tapped. - -### Styling the Snackbar - -To supply a style for the `showSnackbar` function you have to supply a `SnackbarConfig`. Create a new file in your ui folder called `setup_snackbar_ui.dart`. Inside create a function that will register your snackbarConfig. We will register a config that makes the background red, text white and the main button title black. - -```dart -void setupSnackbarUi() { - final service = locator(); - - // Registers a config to be used when calling showSnackbar - service.registerSnackbarConfig(SnackbarConfig( - backgroundColor: Colors.red, - textColor: Colors.white, - mainButtonTextColor: Colors.black, - )); -} -``` - -Then in the main.dart file before running the app, after setting up the locator we call `setupSnackbarUi`. - -```dart -void main() { - setupLocator(); - setupSnackbarUi(); - runApp(MyApp()); -} -``` - -If you now execute the same showSnackbar function as above you'll see the background is red, text white and the action button has black text. - -### Custom Styles - -Sometimes you might want more than 1 snackbar style. In this case you can register multiple SnackbarConfigs to be shown using the `showCustomSnackBar` function. To register a custom config we need to define unique values to register it again that's easy to reference when we want to show the snackbar using that config. I like to use enums. We'll start by creating an enum called `SnackbarType`. - -```dart -/// The type of snackbar to show -enum SnackbarType { blueAndYellow, greenAndRed } -``` - -Then open up the `setup_snackbar_ui.dart` created above and we'll add the configs for the two enums. - -```dart -void setupSnackbarUi() { - final service = locator(); - - service.registerCustomSnackbarConfig( - variant: SnackbarType.blueAndYellow, - config: SnackbarConfig( - backgroundColor: Colors.blueAccent, - textColor: Colors.yellow, - borderRadius: 1, - dismissDirection: SnackDismissDirection.HORIZONTAL, - ), - ); - - service.registerCustomSnackbarConfig( - variant: SnackbarType.greenAndRed, - config: SnackbarConfig( - backgroundColor: Colors.white, - titleColor: Colors.green, - messageColor: Colors.red, - borderRadius: 1, - ), - ); -} -``` - -Now you can call `showCustomSnackBar` and pass in the `variant` enum that you'd like to use. The following code will show the blueAndYellow snackbar. - -```dart -_snackbarService.showCustomSnackBar( - variant: SnackbarType.blueAndYellow, - message: 'Blue and yellow', - title: 'The message is the message', - duration: Duration(seconds: 2), - onTap: (_) { - print('snackbar tapped'); - }, - mainButtonTitle: 'Undo', - onMainButtonTapped: () => print('Undo the action!'), -); -``` - -And the following code will show the greenAndRed snackbar - -```dart -_snackbarService.showCustomSnackBar( - variant: SnackbarType.greenAndRed, - title: 'Green and Red', - message: 'The text is green and red and the background is white', - duration: Duration(seconds: 2), - onTap: (_) { - print('snackbar tapped'); - }, - mainButtonTitle: 'Undo', - onMainButtonTapped: () => print('Undo the action!'), -); -``` - -The snackbar service does not cover every scenario at the moment, especially for adding multiple actions or using icons. If you're looking for those kind of features please make an issue or make a PR for the functionality. I would greatly appreciate it. - -## BottomSheet Service - -This service, similar to the others above, allows the user to show a `BottomSheet` from the same place they handle their business logic. It's calls that can be awaited on for a result returned by the user. This makes writing your business logic much easier in the long run. - -## Usage - -The `BottomSheetService` has a basic mode for quick and dirty bottom sheet functionality, and also has a custom UI building function. To show a basic bottom sheet you simply get the `BottomSheetService` from the locator and then call `showBottomSheet`. - -```dart -final BottomSheetService _bottomSheetService = locator(); - -var sheetResponse = await _bottomSheetService.showBottomSheet( - title: 'This is my Sheets Title', - description: - 'This property will display under the title. We\'re not going to provide a lot of UI versions for the sheet because everyone will have a different style.\nInstead you can use the custom sheet builders as shown below.', -); -``` - -As you can see above you can get a response from the `showBottomSheet` call. There are also two additional titles you can pass into the function. - -```dart - var confirmationResponse = - await _bottomSheetService.showBottomSheet( - title: 'Confirm this action with one of the options below', - description: - 'The result from this call will return a SheetResponse object with confirmed set to true. See the logs where we print out the confirmed value for you.', - confirmButtonTitle: 'I confirm', - cancelButtonTitle: 'I DONT confirm', - ); - - print( 'confirmationResponse confirmed: ${confirmationResponse?.confirmed}'); -``` - -The `confirmButtonTitle` when tapped will return a response where `confirmed` is set to true and the cancel title will return a response where `confired` is set to false. - -### Custom UI Setup - -Custom UI works the same as the `DialogService`. You can create a new file in your ui folder called `setup_bottom_sheet_ui.dart`. Inside this file you'll get the `BottomSheetService` from the locator (make sure it's registered, check beginning of readme). Then you'll construct a map of builders which take a mapping of an enum to a builder function. The builder function expects a `BuildContext`, `SheetRequest` and `Function` which we always call a completer. - -```dart -void setupBottomSheetUi() { - final bottomSheetService = locator(); - - final builders = { - BottomSheetType.FloatingBox: (context, sheetRequest, completer) => - _FloatingBoxBottomSheet(request: sheetRequest, completer: completer) - }; - - bottomSheetService.setCustomSheetBuilders(builders); -} - -class _FloatingBoxBottomSheet extends StatelessWidget { - final SheetRequest request; - final Function(SheetResponse) completer; - const _FloatingBoxBottomSheet({ - Key key, - this.request, - this.completer, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - margin: EdgeInsets.all(25), - padding: EdgeInsets.all(25), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(15), - ), - child: Column( - ... - ), - ); - } -} -``` - -Once you've created the builders you set it on the service through `setCustomSheetBuilder`. Now in your code you can show this specific dialog that you registered. - -```dart - var confirmationResponse = - await _bottomSheetService.showCustomSheet( - variant: BottomSheetType.FloatingBox, - title: 'This is a floating bottom sheet', - description: - 'This sheet is a custom built bottom sheet UI that allows you to show it from any service or viewmodel.', - mainButtonTitle: 'Awesome!', - secondaryButtonTitle: 'This is cool', -); - -``` - -### Returning data from the BottomSheet - -When you want to complete the dialog and return some data all your do is call the completer function and pass the `SheetResponse` that you'd like the awaiting function to receive. - -```dart -onTap: () => completer(SheetResponse(...)) -``` diff --git a/packages/stacked_services/example/.gitignore b/packages/stacked_services/example/.gitignore deleted file mode 100644 index b4a256a25..000000000 --- a/packages/stacked_services/example/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Web related -lib/generated_plugin_registrant.dart - -# Exceptions to above rules. -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/stacked_services/example/.metadata b/packages/stacked_services/example/.metadata deleted file mode 100644 index 4adf4bf02..000000000 --- a/packages/stacked_services/example/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: f139b11009aeb8ed2a3a3aa8b0066e482709dde3 - channel: stable - -project_type: app diff --git a/packages/stacked_services/example/README.md b/packages/stacked_services/example/README.md deleted file mode 100644 index a13562602..000000000 --- a/packages/stacked_services/example/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# example - -A new Flutter project. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) - -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/packages/stacked_services/example/android/.gitignore b/packages/stacked_services/example/android/.gitignore deleted file mode 100644 index bc2100d8f..000000000 --- a/packages/stacked_services/example/android/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java diff --git a/packages/stacked_services/example/android/app/build.gradle b/packages/stacked_services/example/android/app/build.gradle deleted file mode 100644 index 601a994a8..000000000 --- a/packages/stacked_services/example/android/app/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - compileSdkVersion 28 - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - lintOptions { - disable 'InvalidPackage' - } - - defaultConfig { - applicationId "com.example.example" - minSdkVersion 16 - targetSdkVersion 28 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' -} diff --git a/packages/stacked_services/example/android/app/src/debug/AndroidManifest.xml b/packages/stacked_services/example/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index c208884f3..000000000 --- a/packages/stacked_services/example/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/stacked_services/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/stacked_services/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt deleted file mode 100644 index 1656503f3..000000000 --- a/packages/stacked_services/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.example - -import androidx.annotation.NonNull; -import io.flutter.embedding.android.FlutterActivity -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugins.GeneratedPluginRegistrant - -class MainActivity: FlutterActivity() { - override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { - GeneratedPluginRegistrant.registerWith(flutterEngine); - } -} diff --git a/packages/stacked_services/example/android/app/src/main/res/drawable/launch_background.xml b/packages/stacked_services/example/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 304732f88..000000000 --- a/packages/stacked_services/example/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/packages/stacked_services/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/stacked_services/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7..000000000 Binary files a/packages/stacked_services/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_services/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/stacked_services/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79b..000000000 Binary files a/packages/stacked_services/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_services/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/stacked_services/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 09d439148..000000000 Binary files a/packages/stacked_services/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_services/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/stacked_services/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34..000000000 Binary files a/packages/stacked_services/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_services/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/stacked_services/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eeb..000000000 Binary files a/packages/stacked_services/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_services/example/android/app/src/main/res/values/styles.xml b/packages/stacked_services/example/android/app/src/main/res/values/styles.xml deleted file mode 100644 index 00fa4417c..000000000 --- a/packages/stacked_services/example/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/packages/stacked_services/example/android/app/src/profile/AndroidManifest.xml b/packages/stacked_services/example/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index c208884f3..000000000 --- a/packages/stacked_services/example/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/stacked_services/example/android/build.gradle b/packages/stacked_services/example/android/build.gradle deleted file mode 100644 index 3100ad2d5..000000000 --- a/packages/stacked_services/example/android/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -buildscript { - ext.kotlin_version = '1.3.50' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - jcenter() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/packages/stacked_services/example/android/gradle.properties b/packages/stacked_services/example/android/gradle.properties deleted file mode 100644 index 38c8d4544..000000000 --- a/packages/stacked_services/example/android/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.enableR8=true -android.useAndroidX=true -android.enableJetifier=true diff --git a/packages/stacked_services/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/stacked_services/example/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 296b146b7..000000000 --- a/packages/stacked_services/example/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Jun 23 08:50:38 CEST 2017 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/packages/stacked_services/example/android/settings.gradle b/packages/stacked_services/example/android/settings.gradle deleted file mode 100644 index 5a2f14fb1..000000000 --- a/packages/stacked_services/example/android/settings.gradle +++ /dev/null @@ -1,15 +0,0 @@ -include ':app' - -def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() - -def plugins = new Properties() -def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') -if (pluginsFile.exists()) { - pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } -} - -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory -} diff --git a/packages/stacked_services/example/ios/.gitignore b/packages/stacked_services/example/ios/.gitignore deleted file mode 100644 index e96ef602b..000000000 --- a/packages/stacked_services/example/ios/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/packages/stacked_services/example/ios/Flutter/AppFrameworkInfo.plist b/packages/stacked_services/example/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 6b4c0f78a..000000000 --- a/packages/stacked_services/example/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 8.0 - - diff --git a/packages/stacked_services/example/ios/Flutter/Debug.xcconfig b/packages/stacked_services/example/ios/Flutter/Debug.xcconfig deleted file mode 100644 index 592ceee85..000000000 --- a/packages/stacked_services/example/ios/Flutter/Debug.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/packages/stacked_services/example/ios/Flutter/Release.xcconfig b/packages/stacked_services/example/ios/Flutter/Release.xcconfig deleted file mode 100644 index 592ceee85..000000000 --- a/packages/stacked_services/example/ios/Flutter/Release.xcconfig +++ /dev/null @@ -1 +0,0 @@ -#include "Generated.xcconfig" diff --git a/packages/stacked_services/example/ios/Runner.xcodeproj/project.pbxproj b/packages/stacked_services/example/ios/Runner.xcodeproj/project.pbxproj deleted file mode 100644 index 0d45b982d..000000000 --- a/packages/stacked_services/example/ios/Runner.xcodeproj/project.pbxproj +++ /dev/null @@ -1,518 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B80C3931E831B6300D905FE /* App.framework */, - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1020; - ORGANIZATIONNAME = "The Chromium Authors"; - TargetAttributes = { - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.example; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} diff --git a/packages/stacked_services/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/stacked_services/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a16e..000000000 --- a/packages/stacked_services/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/stacked_services/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/stacked_services/example/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 1d526a16e..000000000 --- a/packages/stacked_services/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/stacked_services/example/ios/Runner/AppDelegate.swift b/packages/stacked_services/example/ios/Runner/AppDelegate.swift deleted file mode 100644 index 70693e4a8..000000000 --- a/packages/stacked_services/example/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -import UIKit -import Flutter - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fab2..000000000 --- a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada472..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 28c6bf030..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 2ccbfd967..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index f091b6b0b..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cde12118..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e7e..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index dcdc2306c..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 2ccbfd967..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8f5..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b8609..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b8609..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d164a..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d39..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 6a84f41e1..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index d0e1f5853..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index 0bedcf2fd..000000000 --- a/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ diff --git a/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 89c2725b7..000000000 --- a/packages/stacked_services/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -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. \ No newline at end of file diff --git a/packages/stacked_services/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/stacked_services/example/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index f2e259c7c..000000000 --- a/packages/stacked_services/example/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/stacked_services/example/ios/Runner/Base.lproj/Main.storyboard b/packages/stacked_services/example/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index f3c28516f..000000000 --- a/packages/stacked_services/example/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/stacked_services/example/ios/Runner/Info.plist b/packages/stacked_services/example/ios/Runner/Info.plist deleted file mode 100644 index a060db61e..000000000 --- a/packages/stacked_services/example/ios/Runner/Info.plist +++ /dev/null @@ -1,45 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - example - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - - diff --git a/packages/stacked_services/example/ios/Runner/Runner-Bridging-Header.h b/packages/stacked_services/example/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index 7335fdf90..000000000 --- a/packages/stacked_services/example/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" \ No newline at end of file diff --git a/packages/stacked_services/example/lib/app/locator.config.dart b/packages/stacked_services/example/lib/app/locator.config.dart deleted file mode 100644 index d75bab5b8..000000000 --- a/packages/stacked_services/example/lib/app/locator.config.dart +++ /dev/null @@ -1,40 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ************************************************************************** -// InjectableConfigGenerator -// ************************************************************************** - -import 'package:get_it/get_it.dart' as _i1; -import 'package:injectable/injectable.dart' as _i2; -import 'package:stacked_services/stacked_services.dart' as _i3; - -import '../services/thirdparty_services_module.dart' - as _i4; // ignore_for_file: unnecessary_lambdas - -// ignore_for_file: lines_longer_than_80_chars -/// initializes the registration of provided dependencies inside of [GetIt] -_i1.GetIt $initGetIt(_i1.GetIt get, - {String environment, _i2.EnvironmentFilter environmentFilter}) { - final gh = _i2.GetItHelper(get, environment, environmentFilter); - final thirdPartyServicesModule = _$ThirdPartyServicesModule(); - gh.lazySingleton<_i3.BottomSheetService>( - () => thirdPartyServicesModule.bottomSheetService); - gh.lazySingleton<_i3.DialogService>( - () => thirdPartyServicesModule.dialogService); - gh.lazySingleton<_i3.NavigationService>( - () => thirdPartyServicesModule.navigationService); - gh.lazySingleton<_i3.SnackbarService>( - () => thirdPartyServicesModule.snackbarService); - return get; -} - -class _$ThirdPartyServicesModule extends _i4.ThirdPartyServicesModule { - @override - _i3.BottomSheetService get bottomSheetService => _i3.BottomSheetService(); - @override - _i3.DialogService get dialogService => _i3.DialogService(); - @override - _i3.NavigationService get navigationService => _i3.NavigationService(); - @override - _i3.SnackbarService get snackbarService => _i3.SnackbarService(); -} diff --git a/packages/stacked_services/example/lib/app/locator.dart b/packages/stacked_services/example/lib/app/locator.dart deleted file mode 100644 index 5ccd41d7c..000000000 --- a/packages/stacked_services/example/lib/app/locator.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:get_it/get_it.dart'; -import 'package:injectable/injectable.dart'; -import 'locator.config.dart'; - -final locator = GetIt.instance; - -@injectableInit -void setupLocator() => $initGetIt(locator); diff --git a/packages/stacked_services/example/lib/app/routes.dart b/packages/stacked_services/example/lib/app/routes.dart deleted file mode 100644 index 5b5dd067f..000000000 --- a/packages/stacked_services/example/lib/app/routes.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:auto_route/auto_route_annotations.dart'; -import '../ui/views/home_screen.dart'; -import '../ui/views/first_screen.dart'; -import '../ui/views/second_screen.dart'; - -// Defining routes and global transitions -@CustomAutoRouter( - routes: [ - MaterialRoute( - page: HomeScreen, - name: 'homeScreenRoute', - initial: true, - ), - MaterialRoute( - page: FirstScreen, - name: 'firstScreenRoute', - ), - MaterialRoute( - page: SecondScreen, - name: 'secondScreenRoute', - ), - ], - transitionsBuilder: TransitionsBuilders.zoomIn, - durationInMilliseconds: 400, -) -class $Router {} diff --git a/packages/stacked_services/example/lib/app/routes.gr.dart b/packages/stacked_services/example/lib/app/routes.gr.dart deleted file mode 100644 index ff78acf40..000000000 --- a/packages/stacked_services/example/lib/app/routes.gr.dart +++ /dev/null @@ -1,70 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -// ************************************************************************** -// AutoRouteGenerator -// ************************************************************************** - -// ignore_for_file: public_member_api_docs - -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; - -import '../ui/views/first_screen.dart'; -import '../ui/views/home_screen.dart'; -import '../ui/views/second_screen.dart'; - -class Routes { - static const String homeScreenRoute = '/'; - static const String firstScreenRoute = '/first-screen'; - static const String secondScreenRoute = '/second-screen'; - static const all = { - homeScreenRoute, - firstScreenRoute, - secondScreenRoute, - }; -} - -class Router extends RouterBase { - @override - List get routes => _routes; - final _routes = [ - RouteDef(Routes.homeScreenRoute, page: HomeScreen), - RouteDef(Routes.firstScreenRoute, page: FirstScreen), - RouteDef(Routes.secondScreenRoute, page: SecondScreen), - ]; - @override - Map get pagesMap => _pagesMap; - final _pagesMap = { - HomeScreen: (data) { - final args = data.getArgs( - orElse: () => HomeScreenArguments(), - ); - return MaterialPageRoute( - builder: (context) => HomeScreen(key: args.key), - settings: data, - ); - }, - FirstScreen: (data) { - return MaterialPageRoute( - builder: (context) => const FirstScreen(), - settings: data, - ); - }, - SecondScreen: (data) { - return MaterialPageRoute( - builder: (context) => const SecondScreen(), - settings: data, - ); - }, - }; -} - -/// ************************************************************************ -/// Arguments holder classes -/// ************************************************************************* - -/// HomeScreen arguments holder class -class HomeScreenArguments { - final Key key; - HomeScreenArguments({this.key}); -} diff --git a/packages/stacked_services/example/lib/enums/bottomsheet_type.dart b/packages/stacked_services/example/lib/enums/bottomsheet_type.dart deleted file mode 100644 index 5eede00af..000000000 --- a/packages/stacked_services/example/lib/enums/bottomsheet_type.dart +++ /dev/null @@ -1,6 +0,0 @@ -enum BottomSheetType { - FloatingBox, - ScrollableList, - ImageSheet, - Generic, -} diff --git a/packages/stacked_services/example/lib/enums/dialog_type.dart b/packages/stacked_services/example/lib/enums/dialog_type.dart deleted file mode 100644 index 215b54bbc..000000000 --- a/packages/stacked_services/example/lib/enums/dialog_type.dart +++ /dev/null @@ -1,4 +0,0 @@ -enum DialogType { - Basic, - Generic, -} diff --git a/packages/stacked_services/example/lib/enums/snackbar_type.dart b/packages/stacked_services/example/lib/enums/snackbar_type.dart deleted file mode 100644 index 1b69fa2ce..000000000 --- a/packages/stacked_services/example/lib/enums/snackbar_type.dart +++ /dev/null @@ -1,2 +0,0 @@ -/// The type of snackbar to show -enum SnackbarType { blueAndYellow, greenAndRed } diff --git a/packages/stacked_services/example/lib/main.dart b/packages/stacked_services/example/lib/main.dart deleted file mode 100644 index d2fc9d815..000000000 --- a/packages/stacked_services/example/lib/main.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:example/ui/setup_bottom_sheet_ui.dart'; -import 'package:example/ui/setup_dialog_ui.dart'; -import 'package:example/ui/setup_snackbar_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:stacked_services/stacked_services.dart'; - -import 'app/locator.dart'; -import 'app/routes.gr.dart' as auto_router; - -void main() { - setupLocator(); - setupDialogUi(); - setupSnackbarUi(); - setupBottomSheetUi(); - - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Stacked Services Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - navigatorObservers: [ - StackedService.routeObserver, - _LoggingObserver(), - ], - navigatorKey: StackedService.navigatorKey, - initialRoute: auto_router.Routes.homeScreenRoute, - onGenerateRoute: auto_router.Router().onGenerateRoute, - ); - } -} - -class _LoggingObserver extends NavigatorObserver { - @override - void didPop(Route route, Route previousRoute) { - print( - 'route.name: ${route?.settings?.name}, previousRoute.name: ${previousRoute?.settings?.name}'); - super.didPop(route, previousRoute); - } - - @override - void didRemove(Route route, Route previousRoute) { - print( - 'route.name: ${route?.settings?.name}, previousRoute.name: ${previousRoute?.settings?.name}'); - super.didRemove(route, previousRoute); - } - - @override - void didPush(Route route, Route previousRoute) { - print( - 'route.name: ${route?.settings?.name}, previousRoute.name: ${previousRoute?.settings?.name}'); - super.didPush(route, previousRoute); - } - - @override - void didReplace({Route newRoute, Route oldRoute}) { - print( - 'newRoute.name: ${newRoute?.settings?.name}, oldRoute.name: ${oldRoute?.settings?.name}'); - super.didReplace(newRoute: newRoute, oldRoute: oldRoute); - } -} diff --git a/packages/stacked_services/example/lib/services/thirdparty_services_module.dart b/packages/stacked_services/example/lib/services/thirdparty_services_module.dart deleted file mode 100644 index d64ec4951..000000000 --- a/packages/stacked_services/example/lib/services/thirdparty_services_module.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:injectable/injectable.dart'; -import 'package:stacked_services/stacked_services.dart'; - -@module -abstract class ThirdPartyServicesModule { - @lazySingleton - NavigationService get navigationService; - @lazySingleton - DialogService get dialogService; - @lazySingleton - SnackbarService get snackbarService; - @lazySingleton - BottomSheetService get bottomSheetService; -} diff --git a/packages/stacked_services/example/lib/ui/setup_bottom_sheet_ui.dart b/packages/stacked_services/example/lib/ui/setup_bottom_sheet_ui.dart deleted file mode 100644 index 7e8c02d22..000000000 --- a/packages/stacked_services/example/lib/ui/setup_bottom_sheet_ui.dart +++ /dev/null @@ -1,180 +0,0 @@ -import 'package:example/app/locator.dart'; -import 'package:example/enums/bottomsheet_type.dart'; -import 'package:stacked_services/stacked_services.dart'; -import 'package:flutter/material.dart'; - -void setupBottomSheetUi() { - final bottomSheetService = locator(); - - final builders = { - BottomSheetType.FloatingBox: (context, sheetRequest, completer) => - _FloatingBoxBottomSheet( - request: sheetRequest, - completer: completer, - ), - BottomSheetType.Generic: ( - context, - sheetRequest, - Function(SheetResponse) completer, - ) => - GenericBottomSheet( - request: sheetRequest, - completer: completer, - ), - }; - - bottomSheetService.setCustomSheetBuilders(builders); -} - -class _FloatingBoxBottomSheet extends StatelessWidget { - final SheetRequest request; - final Function(SheetResponse) completer; - const _FloatingBoxBottomSheet({ - Key key, - this.request, - this.completer, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - margin: EdgeInsets.all(25), - padding: EdgeInsets.all(25), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(15), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - request.title, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.grey[900], - ), - ), - SizedBox(height: 10), - Text( - request.description, - style: TextStyle(color: Colors.grey), - ), - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - MaterialButton( - onPressed: () => completer(SheetResponse(confirmed: true)), - child: Text( - request.secondaryButtonTitle, - style: TextStyle(color: Theme.of(context).primaryColor), - ), - ), - TextButton( - onPressed: () => completer(SheetResponse(confirmed: true)), - child: Text( - request.mainButtonTitle, - style: TextStyle(color: Colors.white), - ), - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).primaryColor, - ), - ), - ) - ], - ) - ], - ), - ); - } -} - -class GenericBottomSheetRequest { - const GenericBottomSheetRequest({ - this.message = 'GenericBottomSheetRequest', - }); - - final String message; -} - -class GenericBottomSheetResponse { - const GenericBottomSheetResponse({ - this.message = 'GenericBottomSheetResponse', - }); - - final String message; -} - -class GenericBottomSheet extends StatelessWidget { - final SheetRequest request; - final Function(SheetResponse) completer; - - const GenericBottomSheet({ - Key key, - this.request, - this.completer, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - margin: EdgeInsets.all(25), - padding: EdgeInsets.all(25), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(15), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - request.title ?? 'Generic Bottom Sheet', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.grey[900], - ), - ), - SizedBox(height: 10), - Text( - request.description, - style: TextStyle(color: Colors.grey), - ), - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - MaterialButton( - onPressed: () => completer(SheetResponse( - confirmed: true, - data: GenericBottomSheetResponse(message: 'SecondaryButton'), - )), - child: Text( - request.secondaryButtonTitle, - style: TextStyle(color: Theme.of(context).primaryColor), - ), - ), - TextButton( - onPressed: () => completer(SheetResponse( - confirmed: true, - data: GenericBottomSheetResponse(message: 'MainButton'), - )), - child: Text( - request.mainButtonTitle, - style: TextStyle(color: Colors.white), - ), - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).primaryColor, - ), - ), - ) - ], - ) - ], - ), - ); - } -} diff --git a/packages/stacked_services/example/lib/ui/setup_dialog_ui.dart b/packages/stacked_services/example/lib/ui/setup_dialog_ui.dart deleted file mode 100644 index 6d9ac9dea..000000000 --- a/packages/stacked_services/example/lib/ui/setup_dialog_ui.dart +++ /dev/null @@ -1,158 +0,0 @@ -import 'package:example/app/locator.dart'; -import 'package:example/enums/dialog_type.dart'; -import 'package:flutter/material.dart'; -import 'package:stacked_services/stacked_services.dart'; - -void setupDialogUi() { - var dialogService = locator(); - - final builders = { - DialogType.Basic: (context, sheetRequest, completer) => - _BasicDialog(request: sheetRequest, completer: completer), - DialogType.Generic: (context, sheetRequest, - Function(DialogResponse) completer) => - _GenericDialog( - request: sheetRequest, - completer: completer, - ), - }; - - dialogService.registerCustomDialogBuilders(builders); -} - -class _BasicDialog extends StatelessWidget { - final DialogRequest request; - final Function(DialogResponse) completer; - const _BasicDialog({Key key, this.request, this.completer}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Dialog( - child: Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - request.title, - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 23), - ), - SizedBox( - height: 10, - ), - Text( - request.description, - style: TextStyle( - fontSize: 18, - ), - textAlign: TextAlign.center, - ), - SizedBox( - height: 20, - ), - GestureDetector( - onTap: () => completer(DialogResponse()), - child: Container( - child: request.showIconInMainButton - ? Icon(Icons.check_circle) - : Text(request.mainButtonTitle), - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(vertical: 10), - width: double.infinity, - decoration: BoxDecoration( - color: Colors.redAccent, - borderRadius: BorderRadius.circular(5), - ), - ), - ) - ], - ), - ), - ); - } -} - -class GenericDialogRequest { - const GenericDialogRequest({ - this.message = 'Hello World', - }); - - final String message; -} - -class GenericDialogResponse { - const GenericDialogResponse({ - this.message = 'Hello World', - }); - - final String message; -} - -class _GenericDialog extends StatelessWidget { - final DialogRequest request; - final Function(DialogResponse) completer; - - const _GenericDialog({ - Key key, - this.request, - this.completer, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Dialog( - child: Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - request.title ?? 'Generic Dialog', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 23), - ), - SizedBox( - height: 10, - ), - Text( - request.description, - style: TextStyle( - fontSize: 18, - ), - textAlign: TextAlign.center, - ), - SizedBox( - height: 20, - ), - GestureDetector( - onTap: () => completer( - DialogResponse(data: GenericDialogResponse()), - ), - child: Container( - child: request.showIconInMainButton - ? Icon(Icons.check_circle) - : Text(request.mainButtonTitle), - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(vertical: 10), - width: double.infinity, - decoration: BoxDecoration( - color: Colors.redAccent, - borderRadius: BorderRadius.circular(5), - ), - ), - ) - ], - ), - ), - ); - } -} diff --git a/packages/stacked_services/example/lib/ui/setup_snackbar_ui.dart b/packages/stacked_services/example/lib/ui/setup_snackbar_ui.dart deleted file mode 100644 index 93a8a624b..000000000 --- a/packages/stacked_services/example/lib/ui/setup_snackbar_ui.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:example/app/locator.dart'; -import 'package:example/enums/snackbar_type.dart'; -import 'package:flutter/material.dart'; -import 'package:stacked_services/stacked_services.dart'; - -void setupSnackbarUi() { - final service = locator(); - - // Registers a config to be used when calling showSnackbar - service.registerSnackbarConfig(SnackbarConfig( - backgroundColor: Colors.red, - textColor: Colors.white, - mainButtonTextColor: Colors.black, - )); - - service.registerCustomSnackbarConfig( - variant: SnackbarType.blueAndYellow, - config: SnackbarConfig( - backgroundColor: Colors.blueAccent, - textColor: Colors.yellow, - borderRadius: 1, - dismissDirection: DismissDirection.horizontal, - ), - ); - - service.registerCustomSnackbarConfig( - variant: SnackbarType.greenAndRed, - config: SnackbarConfig( - backgroundColor: Colors.white, - titleColor: Colors.green, - messageColor: Colors.red, - borderRadius: 1, - ), - ); -} diff --git a/packages/stacked_services/example/lib/ui/views/bottom_sheet_view.dart b/packages/stacked_services/example/lib/ui/views/bottom_sheet_view.dart deleted file mode 100644 index 74c52527e..000000000 --- a/packages/stacked_services/example/lib/ui/views/bottom_sheet_view.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'package:example/app/locator.dart'; -import 'package:example/enums/bottomsheet_type.dart'; -import 'package:flutter/material.dart'; -import 'package:stacked_services/stacked_services.dart'; - -import '../setup_bottom_sheet_ui.dart'; - -class BottomSheetView extends StatelessWidget { - BottomSheetView({Key key}) : super(key: key); - - final BottomSheetService _bottomSheetService = locator(); - - @override - Widget build(BuildContext context) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Press the button below to show a regular BottomSheet', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - OutlinedButton( - onPressed: () async { - await _bottomSheetService.showBottomSheet( - title: 'This is my Sheets Title', - description: - 'This property will display under the title. We\'re not going to provide a lot of UI versions for the sheet because everyone will have a different style.\nInstead you can use the custom sheet builders as shown below.', - ); - }, - child: Text( - 'Show Basic Bottom Sheet Alert', - ), - ), - Text( - 'Press the button below to show a confirmation bottom sheet', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - OutlinedButton( - onPressed: () async { - var confirmationResponse = - await _bottomSheetService.showBottomSheet( - title: 'Confirm this action with one of the options below', - description: - 'The result from this call will return a SheetResponse object with confirmed set to true. See the logs where we print out the confirmed value for you.', - confirmButtonTitle: 'I confirm', - cancelButtonTitle: 'I DONT confirm', - ); - - print( - 'confirmationResponse confirmed: ${confirmationResponse?.confirmed}'); - }, - child: Text( - 'Show Confirmation Bottom Sheet', - ), - ), - Text( - 'Press the button below to show one of the custom sheets', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - OutlinedButton( - onPressed: () async { - var confirmationResponse = - await _bottomSheetService.showCustomSheet( - variant: BottomSheetType.FloatingBox, - title: 'This is a floating bottom sheet', - description: - 'This sheet is a custom built bottom sheet UI that allows you to show it from any service or viewmodel.', - mainButtonTitle: 'Awesome!', - secondaryButtonTitle: 'This is cool', - ); - - print( - 'confirmationResponse confirmed: ${confirmationResponse?.confirmed}'); - }, - child: Text( - 'Show Custom Bottom Sheet', - ), - ), - Text( - 'Press the button below to show one of the generic custom sheets', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - OutlinedButton( - onPressed: () async { - final response = await _bottomSheetService.showCustomSheet< - GenericBottomSheetResponse, GenericBottomSheetRequest>( - variant: BottomSheetType.Generic, - title: 'This is a generic bottom sheet', - description: - 'This sheet is a custom built bottom sheet UI that allows you to show it from any service or viewmodel.', - mainButtonTitle: 'Awesome!', - secondaryButtonTitle: 'This is cool', - ); - - print('confirmationResponse confirmed: ${response?.confirmed}'); - - print('response ${response.data.message}'); - }, - child: Text( - 'Show Generic Custom Bottom Sheet', - ), - ), - ], - ), - ); - } -} diff --git a/packages/stacked_services/example/lib/ui/views/dialog_view.dart b/packages/stacked_services/example/lib/ui/views/dialog_view.dart deleted file mode 100644 index 530209bc9..000000000 --- a/packages/stacked_services/example/lib/ui/views/dialog_view.dart +++ /dev/null @@ -1,219 +0,0 @@ -import 'package:example/app/locator.dart'; -import 'package:example/ui/setup_dialog_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:stacked_services/stacked_services.dart'; - -import '../../enums/dialog_type.dart'; - -class DialogView extends StatelessWidget { - DialogView({Key key}) : super(key: key); - - final DialogService _dialogService = locator(); - - @override - Widget build(BuildContext context) { - return Center( - child: ListView( - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Press the button below to show a regular Material dialog', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - OutlinedButton( - onPressed: () async { - await _dialogService.showDialog( - title: 'Test Dialog Title', - description: 'Test Dialog Description', - ); - }, - child: Text( - 'Show Material Dialog', - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'Press the button below to show a regular Material dialog without description', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - ), - OutlinedButton( - onPressed: () async { - await _dialogService.showDialog( - title: 'Test Dialog Title', - // description: 'Test Dialog Description', - ); - }, - child: Text( - 'Show Material Dialog', - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'Press the button below to show a regular Material dialog without title', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - ), - OutlinedButton( - onPressed: () async { - await _dialogService.showDialog( - // title: 'Test Dialog Title', - description: 'Test Dialog Description', - ); - }, - child: Text( - 'Show Material Dialog', - ), - ), - Text( - 'Press the button below to show a custom dialog', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - OutlinedButton( - onPressed: () async { - await _dialogService.showCustomDialog( - variant: DialogType.Basic, - title: 'This is a custom UI with Text as main button', - description: - 'Sheck out the builder in the dialog_ui_register.dart file', - mainButtonTitle: 'Ok', - showIconInMainButton: false, - barrierDismissible: true); - }, - child: Text( - 'Show Custom Text Dialog', - ), - ), - Text( - 'Press the button below to show a Custom Generic dialog', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - OutlinedButton( - onPressed: () async { - final response = await _dialogService.showCustomDialog< - GenericDialogResponse, GenericDialogRequest>( - variant: DialogType.Generic, - title: - 'This is a custom Generic UI with Text as main button', - description: - 'Sheck out the builder in the dialog_ui_register.dart file', - mainButtonTitle: 'Ok', - showIconInMainButton: false, - barrierDismissible: true, - data: GenericDialogRequest(), - ); - - print(response.data.message); - }, - child: Text( - 'Show Generic Dialog', - ), - ), - OutlinedButton( - onPressed: () async { - await _dialogService.showCustomDialog( - variant: DialogType.Basic, - title: 'This is a custom UI with icon', - description: - 'Sheck out the builder in the dialog_ui_register.dart file', - showIconInMainButton: true, - ); - }, - child: Text( - 'Show Custom Icon Dialog', - ), - ), - Text( - 'Press the button below to show a Material confirmation dialog', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - OutlinedButton( - onPressed: () async { - await _dialogService.showConfirmationDialog( - title: 'Test Confirmation Dialog Title', - description: 'Test Confirmation Dialog Description', - barrierDismissible: true, - ); - }, - child: Text( - 'Show Material Confirmation Dialog', - ), - ), - Text( - 'Press the button below to show a Cupertino dialog', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - OutlinedButton( - onPressed: () async { - await _dialogService.showDialog( - dialogPlatform: DialogPlatform.Cupertino, - title: 'Test Confirmation Dialog Title', - description: 'Test Dialog Description', - ); - }, - child: Text( - 'Show Cupertino Dialog', - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'Press the button below to show a Cupertino confirmation dialog', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - ), - OutlinedButton( - onPressed: () async { - await _dialogService.showConfirmationDialog( - dialogPlatform: DialogPlatform.Cupertino, - title: 'Test Confirmation Dialog Title', - description: 'Test Confirmation Dialog Description', - barrierDismissible: true, - ); - }, - child: Text( - 'Show Cupertino Confirmation Dialog', - ), - ), - Text( - "Using Route name to Navigate to next page", - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - ], - ) - ], - ), - ); - } -} diff --git a/packages/stacked_services/example/lib/ui/views/first_screen.dart b/packages/stacked_services/example/lib/ui/views/first_screen.dart deleted file mode 100644 index 515218b56..000000000 --- a/packages/stacked_services/example/lib/ui/views/first_screen.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:example/app/locator.dart'; -import 'package:flutter/material.dart'; -import 'package:stacked_services/stacked_services.dart'; - -import 'second_screen.dart'; - -class FirstScreen extends StatelessWidget { - const FirstScreen({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - // IMPLEMENTATION NOTE: Services should never be used directly in a view refer to - // https://www.filledstacks.com/post/flutter-and-provider-architecture-using-stacked/#how-does-stacked-work - // for more details. - NavigationService _navigationService = locator(); - - return Scaffold( - appBar: AppBar( - title: Text("First Screen"), - centerTitle: true, - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Using custom transitions and passing the \npage instead of Route name", - softWrap: true, - style: TextStyle(fontSize: 16), - textAlign: TextAlign.center, - ), - SizedBox(height: 30), - OutlinedButton( - child: Text("Use Fade Transition"), - onPressed: () async { - await _navigationService.navigateWithTransition( - SecondScreen(), - transition: "fade", - ); - }, - ), - OutlinedButton( - child: Text("Use Right to Left Transition"), - onPressed: () async { - await _navigationService.navigateWithTransition( - SecondScreen(), - transition: "rightToLeft", - ); - }, - ), - OutlinedButton( - child: Text("Use Left to Right Transition"), - onPressed: () async { - await _navigationService.navigateWithTransition( - SecondScreen(), - transition: "leftToRight", - ); - }, - ), - OutlinedButton( - child: Text("Use Cupertino Transition"), - onPressed: () async { - await _navigationService.navigateWithTransition( - SecondScreen(), - transition: "cupertino", - ); - }, - ), - OutlinedButton( - child: Text("Clear Till First and Show"), - onPressed: () async { - await _navigationService.clearTillFirstAndShowView( - SecondScreen(), - ); - }, - ), - ], - ), - ), - ); - } -} diff --git a/packages/stacked_services/example/lib/ui/views/home_screen.dart b/packages/stacked_services/example/lib/ui/views/home_screen.dart deleted file mode 100644 index abcddeeca..000000000 --- a/packages/stacked_services/example/lib/ui/views/home_screen.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:example/app/locator.dart'; -import 'package:example/app/routes.gr.dart'; -import 'package:example/ui/views/bottom_sheet_view.dart'; -import 'package:example/ui/views/dialog_view.dart'; -import 'package:example/ui/views/snackbar_view.dart'; -import 'package:flutter/material.dart'; -import 'package:stacked_services/stacked_services.dart'; - -class HomeScreen extends StatefulWidget { - HomeScreen({Key key}) : super(key: key); - @override - _HomeScreenState createState() => _HomeScreenState(); -} - -class _HomeScreenState extends State { - // IMPLEMENTATION NOTE: Services should never be used directly in a view refer to - // https://www.filledstacks.com/post/flutter-and-provider-architecture-using-stacked/#how-does-stacked-work - // for more details. - NavigationService _navigationService = locator(); - - int currentIndex = 0; - - @override - Widget build(BuildContext context) { - return Scaffold( - bottomNavigationBar: BottomNavigationBar( - items: [ - BottomNavigationBarItem( - icon: Icon(Icons.chat_bubble), - label: 'Dialogs', - ), - BottomNavigationBarItem( - icon: Icon(Icons.add_alert), - label: 'Snackbar', - ), - BottomNavigationBarItem( - icon: Icon(Icons.ad_units), - label: 'BottomSheet', - ), - ], - currentIndex: currentIndex, - onTap: (index) => setState(() { - currentIndex = index; - }), - ), - appBar: AppBar( - title: Text( - "Home Screen", - ), - centerTitle: true, - ), - body: getViewForIndex(currentIndex), - floatingActionButton: FloatingActionButton( - onPressed: () async { - await _navigationService.navigateTo(Routes.firstScreenRoute); - }, - child: Icon(Icons.arrow_forward), - ), - ); - } - - Widget getViewForIndex(int currentIndex) { - switch (currentIndex) { - case 2: - return BottomSheetView(); - case 1: - return SnackbarView(); - case 0: - default: - return DialogView(); - } - } -} diff --git a/packages/stacked_services/example/lib/ui/views/second_screen.dart b/packages/stacked_services/example/lib/ui/views/second_screen.dart deleted file mode 100644 index fe94e733e..000000000 --- a/packages/stacked_services/example/lib/ui/views/second_screen.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter/material.dart'; - -class SecondScreen extends StatelessWidget { - const SecondScreen({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Second"), - centerTitle: true, - ), - body: Center( - child: Text("Second Screen"), - ), - ); - } -} diff --git a/packages/stacked_services/example/lib/ui/views/snackbar_view.dart b/packages/stacked_services/example/lib/ui/views/snackbar_view.dart deleted file mode 100644 index a74401049..000000000 --- a/packages/stacked_services/example/lib/ui/views/snackbar_view.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:example/app/locator.dart'; -import 'package:example/enums/snackbar_type.dart'; -import 'package:flutter/material.dart'; -import 'package:stacked_services/stacked_services.dart'; - -class SnackbarView extends StatelessWidget { - SnackbarView({Key key}) : super(key: key); - - final _snackbarService = locator(); - - @override - Widget build(BuildContext context) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Press the button below to show a regular snackbar', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - OutlinedButton( - onPressed: () async { - _snackbarService.showSnackbar( - message: 'This is a snack bar', - duration: Duration(seconds: 2), - onTap: (_) { - print('snackbar tapped'); - }, - mainButtonTitle: 'Undo', - onMainButtonTapped: () => print('Undo the action!'), - ); - }, - child: Text( - 'Show Snackbar', - ), - ), - Text( - 'Press the button below to show blueAndYellow snackbar', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - OutlinedButton( - onPressed: () async { - _snackbarService.showCustomSnackBar( - variant: SnackbarType.blueAndYellow, - message: 'Blue and yellow', - title: 'The message is the message', - duration: Duration(seconds: 2), - onTap: (_) { - print('snackbar tapped'); - }, - mainButtonTitle: 'Undo', - onMainButtonTapped: () => print('Undo the action!'), - ); - }, - child: Text( - 'Show Blue and Yellow Snackbar', - ), - ), - Text( - 'Press the button below to show a regular snackbar', - softWrap: true, - style: TextStyle( - fontSize: 14, - ), - ), - OutlinedButton( - onPressed: () async { - _snackbarService.showCustomSnackBar( - variant: SnackbarType.greenAndRed, - message: - 'The text is green and red and the background is white', - duration: Duration(seconds: 2), - onTap: (_) { - print('snackbar tapped'); - }, - mainButtonTitle: 'Undo', - onMainButtonTapped: () => print('Undo the action!'), - ); - }, - child: Text( - 'Show Green and Red Snackbar', - ), - ), - ], - ), - ); - } -} diff --git a/packages/stacked_services/example/test/widget_test.dart b/packages/stacked_services/example/test/widget_test.dart deleted file mode 100644 index 570e0e476..000000000 --- a/packages/stacked_services/example/test/widget_test.dart +++ /dev/null @@ -1,8 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -void main() {} diff --git a/packages/stacked_services/example/web/index.html b/packages/stacked_services/example/web/index.html deleted file mode 100644 index 9b7a438f8..000000000 --- a/packages/stacked_services/example/web/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - example - - - - - - - - diff --git a/packages/stacked_services/lib/src/bottom_sheet/bottom_sheet_service.dart b/packages/stacked_services/lib/src/bottom_sheet/bottom_sheet_service.dart deleted file mode 100644 index 32daa08bf..000000000 --- a/packages/stacked_services/lib/src/bottom_sheet/bottom_sheet_service.dart +++ /dev/null @@ -1,174 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:stacked_services/src/models/overlay_request.dart'; -import 'package:stacked_services/src/models/overlay_response.dart'; -import 'dart:convert'; -import 'package:crypto/crypto.dart'; - -import 'bottom_sheet_ui.dart'; - -typedef SheetBuilder = Widget Function( - BuildContext, SheetRequest, void Function(SheetResponse)); - -/// A service that allows you to show a bottom sheet -class BottomSheetService { - Map? _sheetBuilders; - - void setCustomSheetBuilders(Map builders) { - _sheetBuilders = builders; - } - - Future showBottomSheet({ - required String title, - String? description, - String confirmButtonTitle = 'Ok', - String? cancelButtonTitle, - bool enableDrag = true, - bool barrierDismissible = true, - bool isScrollControlled = false, - Duration? exitBottomSheetDuration, - Duration? enterBottomSheetDuration, - bool? ignoreSafeArea, - }) { - return Get.bottomSheet( - Material( - type: MaterialType.transparency, - child: GeneralBottomSheet( - title: title, - description: description ?? '', - confirmButtonTitle: confirmButtonTitle, - cancelButtonTitle: cancelButtonTitle, - onConfirmTapped: () => completeSheet(SheetResponse(confirmed: true)), - onCancelTapped: () => completeSheet(SheetResponse(confirmed: false)), - ), - ), - backgroundColor: Theme.of(Get.context!).brightness == Brightness.light - ? Colors.white - : Colors.grey[800], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(15), - topRight: Radius.circular(15), - ), - ), - isDismissible: barrierDismissible, - isScrollControlled: isScrollControlled, - enableDrag: barrierDismissible && enableDrag, - exitBottomSheetDuration: exitBottomSheetDuration, - enterBottomSheetDuration: enterBottomSheetDuration, - ignoreSafeArea: ignoreSafeArea, - settings: RouteSettings( - name: 'general_${_hashConcateator([ - title, - description, - ])}'), - ); - } - - /// Creates a popup with the given widget, a scale animation, and faded background. - /// - /// The first generic type argument will be the [BottomSheetResponse] - /// while the second generic type argument is the [BottomSheetRequest] - /// - /// e.g. - /// ```dart - /// await _bottomSheetService.showCustomSheet(); - /// ``` - /// - /// Where [GenericBottomSheetResponse] is a defined model response, - /// and [GenericBottomSheetRequest] is the request model. - Future?> showCustomSheet({ - dynamic variant, - String? title, - String? description, - bool hasImage = false, - String? imageUrl, - bool showIconInMainButton = false, - String? mainButtonTitle, - bool showIconInSecondaryButton = false, - String? secondaryButtonTitle, - bool showIconInAdditionalButton = false, - String? additionalButtonTitle, - bool takesInput = false, - Color barrierColor = Colors.black54, - bool barrierDismissible = true, - bool isScrollControlled = false, - String barrierLabel = '', - @Deprecated('Use `data` and pass in a generic type.') dynamic customData, - R? data, - bool enableDrag = true, - Duration? exitBottomSheetDuration, - Duration? enterBottomSheetDuration, - bool? ignoreSafeArea, - }) { - assert( - _sheetBuilders != null, - ''' - There's no sheet builder supplied for the variant:$variant. If you haven't yet setup your - custom builder. Please call the setCustomSheetBuilders function on the service and supply - the UI that you'd like to build for each variant. - - If you have already done that. Make sure that the variant:$variant has a builder associated - with it. - ''', - ); - - final sheetBuilder = _sheetBuilders![variant]; - - return Get.bottomSheet>( - Material( - type: MaterialType.transparency, - child: sheetBuilder!( - Get.context!, - SheetRequest( - title: title, - description: description, - hasImage: hasImage, - imageUrl: imageUrl, - showIconInMainButton: showIconInMainButton, - mainButtonTitle: mainButtonTitle, - showIconInSecondaryButton: showIconInSecondaryButton, - secondaryButtonTitle: secondaryButtonTitle, - showIconInAdditionalButton: showIconInAdditionalButton, - additionalButtonTitle: additionalButtonTitle, - takesInput: takesInput, - customData: customData, - variant: variant, - data: data, - ), - completeSheet, - ), - ), - isDismissible: barrierDismissible, - isScrollControlled: isScrollControlled, - enableDrag: barrierDismissible && enableDrag, - exitBottomSheetDuration: exitBottomSheetDuration, - enterBottomSheetDuration: enterBottomSheetDuration, - ignoreSafeArea: ignoreSafeArea, - settings: RouteSettings( - name: '$variant\_${_hashConcateator([ - title, - description, - mainButtonTitle, - secondaryButtonTitle, - ])}')); - } - - /// Check if bottomsheet is open - bool? get isBottomSheetOpen => Get.isBottomSheetOpen; - - /// Completes the dialog and passes the [response] to the caller - void completeSheet(SheetResponse response) { - Get.back(result: response); - } -} - -String _hashConcateator(List objects) { - return _generateMd5(objects.join('')); -} - -String _generateMd5(String input) { - return md5.convert(utf8.encode(input)).toString(); -} diff --git a/packages/stacked_services/lib/src/bottom_sheet/bottom_sheet_ui.dart b/packages/stacked_services/lib/src/bottom_sheet/bottom_sheet_ui.dart deleted file mode 100644 index 92963a958..000000000 --- a/packages/stacked_services/lib/src/bottom_sheet/bottom_sheet_ui.dart +++ /dev/null @@ -1,146 +0,0 @@ -/// This class contains all the bottom sheet UI's used in the bottom sheet - -import 'package:flutter/material.dart'; -import 'package:stacked_services/src/bottom_sheet/responsive_reducers.dart'; - -class GeneralBottomSheet extends StatelessWidget { - final String title; - final String? description; - final String confirmButtonTitle; - final String? cancelButtonTitle; - final void Function() onConfirmTapped; - final void Function()? onCancelTapped; - final double sidePadding; - const GeneralBottomSheet({ - Key? key, - required this.title, - required this.confirmButtonTitle, - required this.onConfirmTapped, - this.description, - this.cancelButtonTitle, - this.onCancelTapped, - this.sidePadding = 25, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.all(sidePadding), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - if (description != null) verticalSpaceSmall, - if (description != null) - Text( - description!, - style: TextStyle(fontSize: 15, color: Colors.grey), - ), - verticalSpaceMedium, - FullScreenButton( - title: confirmButtonTitle, - onPressed: onConfirmTapped, - ), - verticalSpaceTiny, - if (cancelButtonTitle != null) - FullScreenButton( - title: cancelButtonTitle!, - onPressed: onCancelTapped, - color: Colors.white, - textColor: Theme.of(context).primaryColor, - ), - ], - ), - ); - } -} - -class FullScreenButton extends StatelessWidget { - final double horizontalPadding; - final Color? color; - final Color textColor; - final bool busy; - final String title; - final bool outline; - final void Function()? onPressed; - final bool enabled; - final bool hasDropShadow; - - /// Height of the button. Default value is [screenHeightFraction(context, dividedBy: 18)] - final double? height; - - static BorderRadius _borderRadius = BorderRadius.circular(8); - - FullScreenButton({ - Key? key, - required this.title, - required this.onPressed, - this.horizontalPadding = 25, - this.height, - this.color, - this.textColor = Colors.white, - this.busy = false, - this.outline = false, - this.enabled = true, - this.hasDropShadow = false, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: enabled ? onPressed : null, - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - alignment: Alignment.center, - height: height ?? screenHeightFraction(context, dividedBy: 15), - width: screenWidthFraction(context, offsetBy: horizontalPadding * 2), - decoration: outline - ? BoxDecoration( - border: Border.all( - color: enabled - ? (color ?? Theme.of(context).primaryColor) - : Colors.grey[350]!, - width: 1, - ), - borderRadius: _borderRadius, - ) - : BoxDecoration( - color: enabled - ? (color ?? Theme.of(context).primaryColor) - : Colors.grey[350], - borderRadius: _borderRadius, - boxShadow: [ - if (hasDropShadow) - BoxShadow( - color: Colors.black12, - blurRadius: 3, - ) - ]), - child: busy - ? CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(!enabled - ? Colors.grey[350] - : outline - ? color - : textColor), - ) - : Text( - title, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: !enabled - ? Colors.grey[350] - : outline - ? (color ?? Theme.of(context).primaryColor) - : textColor, - ), - ), - ), - ); - } -} diff --git a/packages/stacked_services/lib/src/bottom_sheet/responsive_reducers.dart b/packages/stacked_services/lib/src/bottom_sheet/responsive_reducers.dart deleted file mode 100644 index 98de83c61..000000000 --- a/packages/stacked_services/lib/src/bottom_sheet/responsive_reducers.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; - -const Widget horizontalSpaceTiny = SizedBox(width: 5.0); -const Widget horizontalSpaceSmall = SizedBox(width: 10.0); -const Widget horizontalSpaceRegular = SizedBox(width: 18.0); -const Widget horizontalSpaceMedium = SizedBox(width: 25.0); -const Widget horizontalSpaceLarge = SizedBox(width: 50.0); - -const double VerticalMediumSpacingAmount = 25; - -const Widget verticalSpaceTiny = SizedBox(height: 5.0); -const Widget verticalSpaceSmall = SizedBox(height: 10.0); -const Widget verticalSpaceRegular = SizedBox(height: 18.0); -const Widget verticalSpaceMedium = - SizedBox(height: VerticalMediumSpacingAmount); -const Widget verticalSpaceLarge = SizedBox(height: 50.0); -const Widget verticalSpaceMassive = SizedBox(height: 120.0); - -Widget spacedDivider = Column( - children: const [ - verticalSpaceMedium, - Divider(color: Colors.blueGrey, height: 5.0), - verticalSpaceMedium, - ], -); - -Widget responsiveVerticalSpace(BuildContext context, {int dividedBy = 1}) => - verticalSpace(screenHeightFraction( - context, - dividedBy: dividedBy, - )); - -Widget responsiveVerticalSpaceSmall(BuildContext context) => - responsiveVerticalSpace(context, dividedBy: 40); - -Widget verticalSpace(double height) => SizedBox(height: height); -Widget horizontalSpace(double height) => SizedBox(width: height); - -double screenWidth(BuildContext context) => MediaQuery.of(context).size.width; -double screenHeight(BuildContext context) => MediaQuery.of(context).size.height; - -double screenHeightPercentage(BuildContext context, - {double percentageOf = 0}) => - screenHeight(context) * percentageOf; - -double screenWidthPercentage(BuildContext context, {double percentageOf = 0}) => - screenWidth(context) * percentageOf; - -double screenHeightFraction(BuildContext context, - {int dividedBy = 1, double offsetBy = 0, double max = 3000}) => - min((screenHeight(context) - offsetBy) / dividedBy, max); - -double screenWidthFraction(BuildContext context, - {int dividedBy = 1, double offsetBy = 0, double max = 3000}) => - min((screenWidth(context) - offsetBy) / dividedBy, max); - -double halfScreenWidth(BuildContext context) => - screenWidthFraction(context, dividedBy: 2); - -double thirdScreenWidth(BuildContext context) => - screenWidthFraction(context, dividedBy: 3); - -/// Returns a font size that simulates 12 on all devices with a max of 14 -double getResponsiveSmallFontSize(BuildContext context) => - getResponsiveFontSize(context, fontSize: 12, max: 14); - -/// Returns a font size that simulates 14 on all devices with a max of 16 -double getResponsiveRegularFontSize(BuildContext context) => - getResponsiveFontSize(context, fontSize: 14, max: 16); - -double getResponsiveFontSize(BuildContext context, - {double fontSize = 14, double? max}) { - if (max == null) { - max = 100; - } - var responsiveSize = - min(screenWidthFraction(context) * (fontSize / 100), max); - - return responsiveSize; -} - -/// This section defines the 4 text sizes used in the app diff --git a/packages/stacked_services/lib/src/dialog/dialog_config.dart b/packages/stacked_services/lib/src/dialog/dialog_config.dart deleted file mode 100644 index 11fe5d983..000000000 --- a/packages/stacked_services/lib/src/dialog/dialog_config.dart +++ /dev/null @@ -1,4 +0,0 @@ -/// Stores the config / styling for the dialog to use -class DialogConfig { - -} diff --git a/packages/stacked_services/lib/src/dialog/dialog_service.dart b/packages/stacked_services/lib/src/dialog/dialog_service.dart deleted file mode 100644 index 892e9052d..000000000 --- a/packages/stacked_services/lib/src/dialog/dialog_service.dart +++ /dev/null @@ -1,270 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:stacked_services/src/dialog/platform_dialog.dart'; -import 'package:stacked_services/src/models/overlay_request.dart'; -import 'package:stacked_services/src/models/overlay_response.dart'; - -typedef DialogBuilder = Widget Function( - BuildContext, DialogRequest, void Function(DialogResponse)); - -enum DialogPlatform { - Cupertino, - Material, - Custom, -} - -/// A DialogService that uses the Get package to show dialogs from the business logic -class DialogService { - Map? _dialogBuilders; - - void registerCustomDialogBuilders(Map builders) { - _dialogBuilders = builders; - } - - Map _customDialogBuilders = - Map(); - - @Deprecated( - 'Prefer to use the StackedServices.navigatorKey instead of using this key. This will be removed in the next major version update for stacked.') - get navigatorKey { - return Get.key; - } - - /// Registers a custom dialog builder. The builder function has been updated to include the function to call - /// when you want to close the dialog. This improves readability and ease of use. When you want to close a dialog - /// and return the result all you do is call the completer function passed in. i.e - /// - /// [registerCustomDialogBuilder](variant: MyDialog.Large, builder: (context, request, completer) => Button(onPressed: () => completer([DialogResponse]()))) - /// - /// The normal completeDialog function will also still work when called on the service - @Deprecated( - 'Prefer to use the registerCustomDialogBuilders() method. This method will be removed on the next major release. 0.7.0', - ) - void registerCustomDialogBuilder({ - required dynamic variant, - required Widget Function( - BuildContext, DialogRequest, Function(DialogResponse)) - builder, - }) { - _customDialogBuilders[variant] = builder; - } - - /// Check if dialog is open - bool? get isDialogOpen => Get.isDialogOpen; - - /// Shows a dialog to the user - /// - /// It will show a platform specific dialog by default. This can be changed by setting [dialogPlatform] - Future showDialog({ - String? title, - String? description, - String? cancelTitle, - Color? cancelTitleColor, - String buttonTitle = 'Ok', - Color? buttonTitleColor, - bool barrierDismissible = false, - - /// Indicates which [DialogPlatform] to show. - /// - /// When not set a Platform specific dialog will be shown - DialogPlatform? dialogPlatform, - }) { - if (dialogPlatform != null) { - return _showDialog( - title: title, - description: description, - cancelTitle: cancelTitle, - cancelTitleColor: cancelTitleColor, - buttonTitle: buttonTitle, - buttonTitleColor: buttonTitleColor, - dialogPlatform: dialogPlatform, - barrierDismissible: barrierDismissible, - ); - } else { - var _dialogType = GetPlatform.isAndroid - ? DialogPlatform.Material - : DialogPlatform.Cupertino; - return _showDialog( - title: title, - description: description, - cancelTitle: cancelTitle, - cancelTitleColor: cancelTitleColor, - buttonTitle: buttonTitle, - buttonTitleColor: buttonTitleColor, - dialogPlatform: _dialogType, - barrierDismissible: barrierDismissible, - ); - } - } - - Future _showDialog({ - String? title, - String? description, - String? cancelTitle, - Color? cancelTitleColor, - String? buttonTitle, - Color? buttonTitleColor, - DialogPlatform dialogPlatform = DialogPlatform.Material, - bool barrierDismissible = false, - }) { - var isConfirmationDialog = cancelTitle != null; - return Get.dialog( - PlatformDialog( - key: Key('dialog_view'), - dialogPlatform: dialogPlatform, - title: title, - content: description, - actions: [ - if (isConfirmationDialog) - PlatformButton( - key: Key('dialog_touchable_cancel'), - textChildKey: Key('dialog_text_cancelButtonText'), - dialogPlatform: dialogPlatform, - text: cancelTitle!, - cancelBtnColor: cancelTitleColor, - isCancelButton: true, - onPressed: () { - completeDialog( - DialogResponse( - confirmed: false, - ), - ); - }, - ), - PlatformButton( - key: Key('dialog_touchable_confirm'), - textChildKey: Key('dialog_text_confirmButtonText'), - dialogPlatform: dialogPlatform, - text: buttonTitle!, - confirmationBtnColor: buttonTitleColor, - onPressed: () { - completeDialog( - DialogResponse( - confirmed: true, - ), - ); - }, - ), - ], - ), - barrierDismissible: barrierDismissible, - ); - } - - /// Creates a popup with the given widget, a scale animation, and faded background. - /// - /// The first generic type argument will be the [DialogResponse] - /// while the second generic type argument is the [DialogRequest] - /// - /// e.g. - /// ```dart - /// await _dialogService.showCustomDialog(); - /// ``` - /// - /// Where [GenericDialogResponse] is a defined model response, - /// and [GenericDialogRequest] is the request model. - Future?> showCustomDialog({ - dynamic variant, - String? title, - String? description, - bool hasImage = false, - String? imageUrl, - bool showIconInMainButton = false, - String? mainButtonTitle, - bool showIconInSecondaryButton = false, - String? secondaryButtonTitle, - bool showIconInAdditionalButton = false, - String? additionalButtonTitle, - bool takesInput = false, - Color barrierColor = Colors.black54, - bool barrierDismissible = false, - String barrierLabel = '', - @Deprecated('Prefer to use `data` and pass in a generic type.') - dynamic customData, - R? data, - }) { - assert( - _dialogBuilders != null, - 'You have to call registerCustomDialogBuilder to use this function. Look at the custom dialog UI section in the stacked_services readme.', - ); - - final customDialogUI = _dialogBuilders![variant]; - - assert( - customDialogUI != null, - 'You have to call registerCustomDialogBuilder to use this function. Look at the custom dialog UI section in the stacked_services readme.', - ); - - return Get.generalDialog>( - barrierColor: barrierColor, - transitionDuration: const Duration(milliseconds: 200), - barrierDismissible: barrierDismissible, - barrierLabel: barrierLabel, - pageBuilder: (BuildContext buildContext, _, __) => SafeArea( - key: Key('dialog_view'), - child: Builder( - builder: (BuildContext context) => customDialogUI!( - context, - DialogRequest( - title: title, - description: description, - hasImage: hasImage, - imageUrl: imageUrl, - showIconInMainButton: showIconInMainButton, - mainButtonTitle: mainButtonTitle, - showIconInSecondaryButton: showIconInSecondaryButton, - secondaryButtonTitle: secondaryButtonTitle, - showIconInAdditionalButton: showIconInAdditionalButton, - additionalButtonTitle: additionalButtonTitle, - takesInput: takesInput, - customData: customData, - data: data, - variant: variant, - ), - completeDialog, - ), - ), - ), - // TODO: Add configurable transition builders to set from the outside as well - // transitionBuilder: (context, animation, _, child) { - // return ScaleTransition( - // scale: CurvedAnimation( - // parent: animation, - // curve: Curves.decelerate, - // ), - // child: child, - // ); - // }, - ); - } - - /// Shows a confirmation dialog with title and description - Future showConfirmationDialog({ - String? title, - String? description, - String cancelTitle = 'Cancel', - String confirmationTitle = 'Ok', - bool barrierDismissible = false, - - /// Indicates which [DialogPlatform] to show. - /// - /// When not set a Platform specific dialog will be shown - DialogPlatform? dialogPlatform, - }) => - showDialog( - title: title, - description: description, - buttonTitle: confirmationTitle, - cancelTitle: cancelTitle, - dialogPlatform: dialogPlatform, - barrierDismissible: barrierDismissible, - ); - - /// Completes the dialog and passes the [response] to the caller - void completeDialog(DialogResponse response) { - Get.back(result: response); - } -} diff --git a/packages/stacked_services/lib/src/dialog/platform_dialog.dart b/packages/stacked_services/lib/src/dialog/platform_dialog.dart deleted file mode 100644 index f8962d9ee..000000000 --- a/packages/stacked_services/lib/src/dialog/platform_dialog.dart +++ /dev/null @@ -1,155 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:stacked_services/stacked_services.dart'; - -const TextStyle _defaultTextStyle = TextStyle(color: Colors.black); -const TextStyle _cancelTextStyle = TextStyle(color: Colors.red); - -class PlatformButton extends StatelessWidget { - final DialogPlatform? dialogPlatform; - final String text; - final void Function() onPressed; - final bool isCancelButton; - final Color? confirmationBtnColor; - final Color? cancelBtnColor; - final Key? textChildKey; - - const PlatformButton({ - Key? key, - this.textChildKey, - this.dialogPlatform, - this.isCancelButton = false, - required this.text, - required this.onPressed, - this.confirmationBtnColor, - this.cancelBtnColor, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - // final isDarkMode = Theme.of(context).brightness == Brightness.dark; - switch (dialogPlatform) { - case DialogPlatform.Cupertino: - return CupertinoDialogAction( - child: Text(text, - key: textChildKey, - style: isCancelButton - ? cancelBtnColor != null - ? TextStyle(color: cancelBtnColor) - : _cancelTextStyle - : confirmationBtnColor != null - ? TextStyle(color: confirmationBtnColor) - : null), - onPressed: onPressed, - ); - - case DialogPlatform.Material: - default: - return TextButton( - child: Text(text, - key: textChildKey, - style: isCancelButton - ? cancelBtnColor != null - ? TextStyle(color: cancelBtnColor) - : _cancelTextStyle - : confirmationBtnColor != null - ? TextStyle(color: confirmationBtnColor) - : null), - onPressed: onPressed, - ); - } - } -} - -class PlatformDialog extends StatelessWidget { - /// The title of the dialog is displayed in a large font at the top - final String? title; - - /// Padding around the title. - /// - /// If there is no title, no padding will be provided. Otherwise, this padding - /// is used. - /// - /// This property defaults to providing 24 pixels on the top, left, and right - /// of the title. If the [content] is not null, then no bottom padding is - /// provided (but see [contentPadding]). If it _is_ null, then an extra 20 - /// pixels of bottom padding is added to separate the [title] from the - /// [actions]. - final EdgeInsetsGeometry? titlePadding; - - /// Style for the text in the [title] of this [AlertDialog]. - final TextStyle titleTextStyle; - - /// The content of the dialog is displayed in the center of the dialog - final String? content; - - /// Padding around the content. - - final EdgeInsetsGeometry contentPadding; - - /// Style for the text in the [content] of this [AlertDialog]. - final TextStyle contentTextStyle; - - /// The set of actions that are displayed at the bottom of the - /// dialog. - final List? actions; - - final DialogPlatform dialogPlatform; - - final String? cancelText; - - const PlatformDialog({ - Key? key, - this.title, - this.titlePadding, - this.titleTextStyle = _defaultTextStyle, - this.content, - this.contentPadding = const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 24.0), - this.contentTextStyle = _defaultTextStyle, - this.actions, - this.dialogPlatform = DialogPlatform.Material, - this.cancelText, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - // final isDarkMode = Theme.of(context).brightness == Brightness.dark; - switch (dialogPlatform) { - case DialogPlatform.Cupertino: - return CupertinoAlertDialog( - title: title != null - ? Text( - title!, - key: Key('dialog_text_title'), - ) - : null, - content: content != null - ? Text( - content!, - key: Key('dialog_text_content'), - ) - : null, - actions: actions ?? [], - ); - case DialogPlatform.Material: - default: - return AlertDialog( - titleTextStyle: Theme.of(context).dialogTheme.titleTextStyle, - contentTextStyle: Theme.of(context).dialogTheme.contentTextStyle, - title: title != null - ? Text( - title!, - key: Key('dialog_text_title'), - ) - : null, - content: content != null - ? Text( - content!, - key: Key('dialog_text_content'), - ) - : null, - actions: actions, - ); - } - } -} diff --git a/packages/stacked_services/lib/src/exceptions/custom_snackbar_exception.dart b/packages/stacked_services/lib/src/exceptions/custom_snackbar_exception.dart deleted file mode 100644 index c596694b0..000000000 --- a/packages/stacked_services/lib/src/exceptions/custom_snackbar_exception.dart +++ /dev/null @@ -1,9 +0,0 @@ -class CustomSnackbarException implements Exception { - final String message; - CustomSnackbarException(this.message); - - @override - String toString() { - return 'CustomSnackbarException: $message'; - } -} diff --git a/packages/stacked_services/lib/src/models/overlay_request.dart b/packages/stacked_services/lib/src/models/overlay_request.dart deleted file mode 100644 index bb470f29f..000000000 --- a/packages/stacked_services/lib/src/models/overlay_request.dart +++ /dev/null @@ -1,132 +0,0 @@ -class OverlayRequest { - /// The title for the dialog - final String? title; - - /// Text so show in the dialog body - final String? description; - - /// Indicates if an image should be used or not - final bool? hasImage; - - /// The url / path to the image to show - final String? imageUrl; - - /// The text shown in the main button - final String? mainButtonTitle; - - /// A bool to indicate if you should show an icon in the main button - final bool? showIconInMainButton; - - /// The text to show on the secondary button on the dialog (cancel usually) - final String? secondaryButtonTitle; - - /// Indicates if you should show an icon in the main button - final bool? showIconInSecondaryButton; - - /// The text show on the third button on the dialog - final String? additionalButtonTitle; - - /// Indicates if you should show an icon in the additional button - final bool? showIconInAdditionalButton; - - /// Indicates if the dialog takes input - final bool? takesInput; - - /// Intended to be used with enums. If you want to create multiple different - /// dialogs. Pass your enum in here and check the value in the builder - final dynamic variant; - - /// Extra data to be passed to the UI - final dynamic customData; - - /// Extra data to be passed to the UI - final T? data; - - OverlayRequest({ - this.showIconInMainButton, - this.showIconInSecondaryButton, - this.showIconInAdditionalButton, - this.title, - this.description, - this.hasImage, - this.imageUrl, - this.mainButtonTitle, - this.secondaryButtonTitle, - this.additionalButtonTitle, - this.takesInput, - @Deprecated('Prefer to use `data` and pass in a generic type.') - this.customData, - this.data, - this.variant, - }); -} - -class DialogRequest extends OverlayRequest { - DialogRequest({ - bool? showIconInMainButton, - bool? showIconInSecondaryButton, - bool? showIconInAdditionalButton, - String? title, - String? description, - bool? hasImage, - String? imageUrl, - String? mainButtonTitle, - String? secondaryButtonTitle, - String? additionalButtonTitle, - bool? takesInput, - @Deprecated('Prefer to use `data` and pass in a generic type.') - dynamic customData, - T? data, - dynamic variant, - }) : super( - additionalButtonTitle: additionalButtonTitle, - customData: customData, - description: description, - hasImage: hasImage, - imageUrl: imageUrl, - mainButtonTitle: mainButtonTitle, - secondaryButtonTitle: secondaryButtonTitle, - showIconInAdditionalButton: showIconInAdditionalButton, - showIconInMainButton: showIconInMainButton, - showIconInSecondaryButton: showIconInSecondaryButton, - takesInput: takesInput, - title: title, - data: data, - variant: variant, - ); -} - -class SheetRequest extends OverlayRequest { - SheetRequest({ - bool? showIconInMainButton, - bool? showIconInSecondaryButton, - bool? showIconInAdditionalButton, - String? title, - String? description, - bool? hasImage, - String? imageUrl, - String? mainButtonTitle, - String? secondaryButtonTitle, - String? additionalButtonTitle, - bool? takesInput, - @Deprecated('Prefer to use `data` and pass in a generic type.') - dynamic customData, - T? data, - dynamic variant, - }) : super( - additionalButtonTitle: additionalButtonTitle, - customData: customData, - description: description, - hasImage: hasImage, - imageUrl: imageUrl, - mainButtonTitle: mainButtonTitle, - secondaryButtonTitle: secondaryButtonTitle, - showIconInAdditionalButton: showIconInAdditionalButton, - showIconInMainButton: showIconInMainButton, - showIconInSecondaryButton: showIconInSecondaryButton, - takesInput: takesInput, - title: title, - variant: variant, - data: data, - ); -} diff --git a/packages/stacked_services/lib/src/models/overlay_response.dart b/packages/stacked_services/lib/src/models/overlay_response.dart deleted file mode 100644 index 990997502..000000000 --- a/packages/stacked_services/lib/src/models/overlay_response.dart +++ /dev/null @@ -1,48 +0,0 @@ -class OverlayResponse { - /// Indicates if a show confirmation call has been confirmed or rejected. - /// null will be returned when it's not a confirmation dialog. - final bool confirmed; - - /// A place to put any response data from dialogs that may contain text fields - /// or multi selection options - final dynamic responseData; - - /// A place to put any response data from dialogs that may contain text fields - /// or multi selection options - final T? data; - - OverlayResponse({ - this.confirmed = false, - @Deprecated('Prefer to use `data` and pass in a generic type.') - this.responseData, - this.data, - }); -} - -/// The response returned from awaiting a call on the [DialogService] -class DialogResponse extends OverlayResponse { - DialogResponse({ - bool confirmed = false, - @Deprecated('Prefer to use `data` and pass in a generic type.') - dynamic responseData, - T? data, - }) : super( - confirmed: confirmed, - responseData: responseData, - data: data, - ); -} - -/// The response returned from awaiting a call on the [BottomSheetService] -class SheetResponse extends OverlayResponse { - SheetResponse({ - bool confirmed = false, - @Deprecated('Prefer to use `data` and pass in a generic type.') - dynamic responseData, - T? data, - }) : super( - confirmed: confirmed, - responseData: responseData, - data: data, - ); -} diff --git a/packages/stacked_services/lib/src/navigation_service.dart b/packages/stacked_services/lib/src/navigation_service.dart deleted file mode 100644 index 6ac7fcbdc..000000000 --- a/packages/stacked_services/lib/src/navigation_service.dart +++ /dev/null @@ -1,311 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:stacked_services/stacked_services.dart'; - -class NavigationTransition { - static const String Fade = 'fade'; - static const String RightToLeft = 'righttoleft'; - static const String LeftToRight = 'lefttoright'; - static const String UpToDown = 'uptodown'; - static const String DownToUp = 'downtoup'; - static const String Rotate = 'zoom'; - static const String RightToLeftWithFade = 'righttoleftwithfade'; - static const String LeftToRighttWithFade = 'lefttorightwithfade'; -} - -/// Provides a service that can be injected into the ViewModels for navigation. -/// -/// Uses the Get library for all navigation requirements -class NavigationService { - Map _transitions = { - NavigationTransition.Fade: Transition.fade, - NavigationTransition.RightToLeft: Transition.rightToLeft, - NavigationTransition.LeftToRight: Transition.leftToRight, - NavigationTransition.UpToDown: Transition.upToDown, - NavigationTransition.DownToUp: Transition.downToUp, - NavigationTransition.Rotate: Transition.zoom, - NavigationTransition.RightToLeftWithFade: Transition.rightToLeftWithFade, - NavigationTransition.LeftToRighttWithFade: Transition.leftToRightWithFade, - }; - - @Deprecated( - 'Prefer to use the StackedServices.navigatorKey instead of using this key. This will be removed in the next major version update for stacked.') - GlobalKey? get navigatorKey => Get.key; - - /// Returns the previous route - String get previousRoute => Get.previousRoute; - - /// Returns the current route - String get currentRoute => Get.currentRoute; - - /// Returns the current arguments - dynamic get currentArguments => Get.arguments; - - /// Creates and/or returns a new navigator key based on the index passed in - @Deprecated( - 'Prefer to use the StackedServices.nestedNavigationKey instead of using this property. This will be removed in the next major version update for stacked.') - GlobalKey? nestedNavigationKey(int index) => - Get.nestedKey(index); - - /// Allows you to configure the default behaviour for navigation. - /// - /// [defaultTransition] can be set using the static members of [NavigationTransition] - /// - /// If you want to use the string directly. Defined [transition] values are - /// - fade - /// - rightToLeft - /// - leftToRight - /// - upToDown - /// - downToUp - /// - scale - /// - rotate - /// - size - /// - rightToLeftWithFade - /// - leftToRightWithFade - /// - cupertino - void config( - {bool? enableLog, - bool? defaultPopGesture, - bool? defaultOpaqueRoute, - Duration? defaultDurationTransition, - bool? defaultGlobalState, - String? defaultTransition}) { - Get.config( - enableLog: enableLog, - defaultPopGesture: defaultPopGesture, - defaultOpaqueRoute: defaultOpaqueRoute, - defaultDurationTransition: defaultDurationTransition, - defaultGlobalState: defaultGlobalState, - defaultTransition: _getTransitionOrDefault(defaultTransition!)); - } - - /// Pushes [page] onto the navigation stack. This uses the [page] itself (Widget) instead - /// of routeName (String). - /// - /// Defined [transition] values can be accessed as static memebers of [NavigationTransition] - /// - /// If you want to use the string directly. Defined [transition] values are - /// - fade - /// - rightToLeft - /// - leftToRight - /// - upToDown - /// - downToUp - /// - scale - /// - rotate - /// - size - /// - rightToLeftWithFade - /// - leftToRightWithFade - /// - cupertino - Future? navigateWithTransition( - Widget page, { - bool? opaque, - String transition = '', - Duration? duration, - bool? popGesture, - int? id, - Curve? curve, - Bindings? binding, - bool fullscreenDialog = false, - bool preventDuplicates = true, - Transition? transitionClass, - }) { - return Get.to( - () => page, - transition: transitionClass ?? _getTransitionOrDefault(transition), - duration: duration ?? Get.defaultTransitionDuration, - popGesture: popGesture ?? Get.isPopGestureEnable, - opaque: opaque ?? Get.isOpaqueRouteDefault, - id: id, - preventDuplicates: preventDuplicates, - curve: curve, - binding: binding, - fullscreenDialog: fullscreenDialog, - ); - } - - /// Replaces current view in the navigation stack. This uses the [page] itself (Widget) instead - /// of routeName (String). - /// - /// Defined [transition] values can be accessed as static memebers of [NavigationTransition] - /// - /// If you want to use the string directly. Defined [transition] values are - /// - fade - /// - rightToLeft - /// - leftToRight - /// - upToDown - /// - downToUp - /// - scale - /// - rotate - /// - size - /// - rightToLeftWithFade - /// - leftToRightWithFade - /// - cupertino - Future? replaceWithTransition( - Widget page, { - bool? opaque, - String transition = '', - Duration? duration, - bool? popGesture, - int? id, - Curve? curve, - Bindings? binding, - bool fullscreenDialog = false, - bool preventDuplicates = true, - Transition? transitionClass, - }) { - return Get.off( - () => page, - transition: transitionClass ?? _getTransitionOrDefault(transition), - duration: duration ?? Get.defaultTransitionDuration, - popGesture: popGesture ?? Get.isPopGestureEnable, - opaque: opaque ?? Get.isOpaqueRouteDefault, - id: id, - preventDuplicates: preventDuplicates, - curve: curve, - binding: binding, - fullscreenDialog: fullscreenDialog, - ); - } - - /// Pops the current scope and indicates if you can pop again - bool back({dynamic result, int? id}) { - Get.back(result: result, id: id); - return Get.key.currentState?.canPop() ?? false; - } - - /// Pops the back stack until the predicate is satisfied - void popUntil(RoutePredicate predicate) { - Get.key.currentState?.popUntil(predicate); - } - - /// Pops the back stack the number of times you indicate with [popTimes] - void popRepeated(int popTimes) { - Get.close(popTimes); - } - - /// Pushes [routeName] onto the navigation stack - Future? navigateTo( - String routeName, { - dynamic arguments, - int? id, - bool preventDuplicates = true, - Map? parameters, - }) { - return Get.toNamed( - routeName, - arguments: arguments, - id: id, - preventDuplicates: preventDuplicates, - parameters: parameters, - ); - } - - /// Pushes [view] onto the navigation stack - Future? navigateToView( - Widget view, { - dynamic arguments, - int? id, - bool? opaque, - Curve? curve, - Bindings? binding, - Duration? duration, - bool fullscreenDialog = false, - bool? popGesture, - bool preventDuplicates = true, - Transition? transition, - }) { - return Get.to( - () => view, - arguments: arguments, - id: id, - opaque: opaque, - preventDuplicates: preventDuplicates, - curve: curve, - binding: binding, - duration: duration, - fullscreenDialog: fullscreenDialog, - popGesture: popGesture, - transition: transition, - ); - } - - /// Replaces the current route with the [routeName] - Future? replaceWith( - String routeName, { - dynamic arguments, - int? id, - bool preventDuplicates = true, - Map? parameters, - }) { - return Get.offNamed( - routeName, - arguments: arguments, - id: id, - preventDuplicates: preventDuplicates, - parameters: parameters, - ); - } - - /// Clears the entire back stack and shows [routeName] - Future? clearStackAndShow( - String routeName, { - dynamic arguments, - int? id, - Map? parameters, - }) { - return Get.offAllNamed( - routeName, - arguments: arguments, - id: id, - parameters: parameters, - ); - } - - /// Pops the navigation stack until there's 1 view left then pushes [routeName] onto the stack - Future? clearTillFirstAndShow( - String routeName, { - dynamic arguments, - int? id, - bool preventDuplicates = true, - Map? parameters, - }) { - _clearBackstackTillFirst(); - - return navigateTo( - routeName, - arguments: arguments, - id: id, - preventDuplicates: preventDuplicates, - parameters: parameters, - ); - } - - /// Pops the navigation stack until there's 1 view left then pushes [view] onto the stack - Future? clearTillFirstAndShowView(Widget view, - {dynamic arguments, int? id}) { - _clearBackstackTillFirst(); - - return navigateToView(view, arguments: arguments, id: id); - } - - /// Push route and clear stack until predicate is satisfied - Future? pushNamedAndRemoveUntil(String routeName, - {RoutePredicate? predicate, dynamic arguments, int? id}) { - return Get.offAllNamed( - routeName, - predicate: predicate, - arguments: arguments, - id: id, - ); - } - - void _clearBackstackTillFirst() { - StackedService.navigatorKey?.currentState - ?.popUntil((Route route) => route.isFirst); - } - - Transition? _getTransitionOrDefault(String transition) { - String _transition = transition.toLowerCase(); - return _transitions[_transition] ?? Get.defaultTransition; - } -} diff --git a/packages/stacked_services/lib/src/route_observer.dart b/packages/stacked_services/lib/src/route_observer.dart deleted file mode 100644 index c30dd63e0..000000000 --- a/packages/stacked_services/lib/src/route_observer.dart +++ /dev/null @@ -1,140 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:get/get_navigation/src/dialog/dialog_route.dart'; -import 'package:get/get_navigation/src/router_report.dart'; - -String? _extractRouteName(Route? route) { - if (route?.settings.name != null) { - return route!.settings.name; - } - - if (route is GetPageRoute) { - return route.routeName; - } - - if (route is GetDialogRoute) { - return 'DIALOG ${route.hashCode}'; - } - - if (route is GetModalBottomSheetRoute) { - return 'BOTTOMSHEET ${route.hashCode}'; - } - - return null; -} - -class _RouteData { - final bool isGetPageRoute; - final bool isBottomSheet; - final bool isDialog; - final String? name; - - _RouteData({ - required this.name, - required this.isGetPageRoute, - required this.isBottomSheet, - required this.isDialog, - }); - - factory _RouteData.ofRoute(Route? route) { - return _RouteData( - name: _extractRouteName(route), - isGetPageRoute: route is GetPageRoute, - isDialog: route is GetDialogRoute, - isBottomSheet: route is GetModalBottomSheetRoute, - ); - } -} - -class StackObserver extends NavigatorObserver { - StackObserver({Routing? routeSend}) : _routeSend = routeSend ?? Get.routing; - - final Routing? _routeSend; - - @override - void didPush(Route route, Route? previousRoute) { - super.didPush(route, previousRoute); - final newRoute = _RouteData.ofRoute(route); - - RouterReportManager.reportCurrentRoute(route); - _routeSend?.update((value) { - if (route is PageRoute) { - value.current = newRoute.name ?? ''; - } - - value.args = route.settings.arguments; - value.route = route; - value.isBack = false; - value.removed = ''; - value.previous = _extractRouteName(previousRoute) ?? ''; - value.isBottomSheet = - newRoute.isBottomSheet ? true : value.isBottomSheet ?? false; - value.isDialog = newRoute.isDialog ? true : value.isDialog ?? false; - }); - } - - @override - void didPop(Route route, Route? previousRoute) { - super.didPop(route, previousRoute); - final newRoute = _RouteData.ofRoute(previousRoute); - - RouterReportManager.reportCurrentRoute(route); - _routeSend?.update((value) { - if (previousRoute is PageRoute) { - value.current = _extractRouteName(previousRoute) ?? ''; - } - - value.args = route.settings.arguments; - value.route = previousRoute; - value.isBack = true; - value.removed = ''; - value.previous = newRoute.name ?? ''; - - value.isBottomSheet = newRoute.isBottomSheet; - value.isDialog = newRoute.isDialog; - }); - } - - @override - void didReplace({Route? newRoute, Route? oldRoute}) { - super.didReplace(newRoute: newRoute, oldRoute: oldRoute); - final newName = _extractRouteName(newRoute); - final oldName = _extractRouteName(oldRoute); - final currentRoute = _RouteData.ofRoute(oldRoute); - - if (newRoute != null) RouterReportManager.reportCurrentRoute(newRoute); - _routeSend?.update((value) { - if (newRoute is PageRoute) { - value.current = newName ?? ''; - } - - value.args = newRoute?.settings.arguments; - value.route = newRoute; - value.isBack = false; - value.removed = ''; - value.previous = '$oldName'; - - value.isBottomSheet = - currentRoute.isBottomSheet ? false : value.isBottomSheet; - value.isDialog = currentRoute.isDialog ? false : value.isDialog; - }); - } - - @override - void didRemove(Route route, Route? previousRoute) { - super.didRemove(route, previousRoute); - final routeName = _extractRouteName(route); - final currentRoute = _RouteData.ofRoute(route); - - _routeSend?.update((value) { - value.route = previousRoute; - value.isBack = false; - value.removed = routeName ?? ''; - value.previous = routeName ?? ''; - - value.isBottomSheet = - currentRoute.isBottomSheet ? false : value.isBottomSheet; - value.isDialog = currentRoute.isDialog ? false : value.isDialog; - }); - } -} diff --git a/packages/stacked_services/lib/src/snackbar/snackbar_config.dart b/packages/stacked_services/lib/src/snackbar/snackbar_config.dart deleted file mode 100644 index ab72d2b10..000000000 --- a/packages/stacked_services/lib/src/snackbar/snackbar_config.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:stacked_services/stacked_services.dart'; -import '../../stacked_services.dart'; - -/// Stores the configuration for the visual appearance of a snackbar -class SnackbarConfig { - Widget? titleText; - Widget? messageText; - Widget? icon; - - /// with instantInit = false you can put Get.snackbar on initState - bool instantInit; - bool shouldIconPulse; - double? maxWidth; - EdgeInsets? margin; - EdgeInsets padding; - double borderRadius; - Color? borderColor; - double borderWidth; - Color backgroundColor; - Color? leftBarIndicatorColor; - - /// Sets the color of the title text regardless of [textColor] - Color? titleColor; - - /// Sets the color of all the test - Color textColor; - - /// Sets the color of the message text regardless of [textColor] - Color? messageColor; - - /// Sets the color of the main button text regardless of [textColor] - Color? mainButtonTextColor; - - List? boxShadows; - Gradient? backgroundGradient; - bool isDismissible; - bool showProgressIndicator; - AnimationController? progressIndicatorController; - Color? progressIndicatorBackgroundColor; - Animation? progressIndicatorValueColor; - SnackPosition snackPosition; - SnackStyle snackStyle; - Curve forwardAnimationCurve; - Curve reverseAnimationCurve; - Duration animationDuration; - double barBlur; - double overlayBlur; - Color overlayColor; - Form? userInputForm; - TextAlign titleTextAlign; - TextAlign messageTextAlign; - DismissDirection dismissDirection; - - SnackbarConfig({ - this.titleText, - this.messageText, - this.icon, - this.textColor = Colors.white, - this.titleColor, - this.messageColor, - this.mainButtonTextColor, - this.instantInit = false, - this.shouldIconPulse = true, - this.maxWidth, - this.margin, - this.padding = const EdgeInsets.all(16), - this.borderRadius = 0.0, - this.borderColor, - this.borderWidth = 1.0, - this.backgroundColor = const Color(0xFF303030), - this.leftBarIndicatorColor, - this.boxShadows, - this.backgroundGradient, - this.isDismissible = true, - this.dismissDirection = DismissDirection.vertical, - this.showProgressIndicator = false, - this.progressIndicatorController, - this.progressIndicatorBackgroundColor, - this.progressIndicatorValueColor, - this.snackPosition = SnackPosition.BOTTOM, - this.snackStyle = SnackStyle.FLOATING, - this.forwardAnimationCurve = Curves.easeOutCirc, - this.reverseAnimationCurve = Curves.easeOutCirc, - this.animationDuration = const Duration(seconds: 1), - this.barBlur = 0.0, - this.overlayBlur = 0.0, - this.overlayColor = Colors.transparent, - this.userInputForm, - this.titleTextAlign = TextAlign.left, - this.messageTextAlign = TextAlign.left, - }); -} diff --git a/packages/stacked_services/lib/src/snackbar/snackbar_service.dart b/packages/stacked_services/lib/src/snackbar/snackbar_service.dart deleted file mode 100644 index a4d2f91ed..000000000 --- a/packages/stacked_services/lib/src/snackbar/snackbar_service.dart +++ /dev/null @@ -1,259 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:stacked_services/src/exceptions/custom_snackbar_exception.dart'; -import 'package:stacked_services/src/snackbar/snackbar_config.dart'; - -/// A service that allows the user to show the snackbar from a ViewModel -class SnackbarService { - @Deprecated( - 'Prefer to use the StackedServices.navigatorKey instead of using this key. This will be removed in the next major version update for stacked.') - get navigatorKey { - return Get.key; - } - - Map _customSnackbarConfigs = - Map(); - - Map _customSnackbarConfigBuilders = - Map(); - - Map _mainButtonBuilder = - Map(); - - SnackbarConfig? _snackbarConfig; - - /// Checks if there is a snackbar open - bool? get isOpen => Get.isSnackbarOpen; - - /// Saves the [config] to be used for the [showSnackbar] function - void registerSnackbarConfig(SnackbarConfig config) => - _snackbarConfig = config; - - /// Saves the [config] against the value of [customData] - @Deprecated( - 'Prefer to use the registerCustomSnackbarConfig() method. Will be removed in future release') - void registerCustomSnackbarconfig({ - @required dynamic customData, - @required SnackbarConfig? config, - }) => - registerCustomSnackbarConfig( - variant: customData, - config: config, - ); - - /// Registers a builder that will be used when showing a matching variant value. The builder - /// function takes in a [String] to display as the title and a `Function` to be used to the - /// onTap callback - void registerCustomMainButtonBuilder({ - @required dynamic variant, - @required Widget Function(String?, Function?)? builder, - }) => - _mainButtonBuilder[variant] = builder; - - /// Saves the [config] against the value of [variant]. A [configBuilder] can also be - /// supplied which will be chosen over the config for the same variant when requested. - void registerCustomSnackbarConfig({ - required dynamic variant, - SnackbarConfig? config, - SnackbarConfig Function()? configBuilder, - }) { - _customSnackbarConfigs[variant] = config; - _customSnackbarConfigBuilders[variant] = configBuilder; - } - - /// Check if snackbar is open - bool? get isSnackbarOpen => Get.isSnackbarOpen; - - /// Shows a snack bar with the details passed in - void showSnackbar({ - String title = '', - required String message, - Function(dynamic)? onTap, - Duration duration = const Duration(seconds: 3), - String? mainButtonTitle, - void Function()? onMainButtonTapped, - }) { - final mainButtonWidget = _getMainButtonWidget( - mainButtonTitle: mainButtonTitle, - onMainButtonTapped: onMainButtonTapped, - config: _snackbarConfig, - ); - - Get.snackbar( - title, - message, - titleText: _snackbarConfig?.titleColor != null || title.isNotEmpty - ? Text( - title, - key: Key('snackbar_text_title'), - style: TextStyle( - color: _snackbarConfig?.titleColor, - fontWeight: FontWeight.w800, - fontSize: 16, - ), - textAlign: _snackbarConfig?.titleTextAlign ?? TextAlign.left, - ) - : SizedBox.shrink(), - messageText: _snackbarConfig?.messageColor != null || message.isNotEmpty - ? Text( - message, - key: Key('snackbar_text_message'), - style: TextStyle( - color: _snackbarConfig?.messageColor, - fontWeight: FontWeight.w300, - fontSize: 14, - ), - textAlign: _snackbarConfig?.messageTextAlign ?? TextAlign.left, - ) - : SizedBox.shrink(), - colorText: _snackbarConfig?.textColor ?? Colors.white, - shouldIconPulse: _snackbarConfig?.shouldIconPulse, - onTap: onTap, - barBlur: _snackbarConfig?.barBlur, - isDismissible: _snackbarConfig?.isDismissible ?? true, - duration: duration, - snackPosition: _snackbarConfig?.snackPosition ?? SnackPosition.BOTTOM, - backgroundColor: _snackbarConfig?.backgroundColor ?? Colors.grey[800], - margin: _snackbarConfig?.margin ?? - const EdgeInsets.symmetric(horizontal: 10, vertical: 25), - mainButton: mainButtonWidget, - ); - } - - Future? showCustomSnackBar({ - required String message, - @deprecated dynamic customData, - dynamic variant, - String? title, - String? mainButtonTitle, - void Function()? onMainButtonTapped, - Function? onTap, - Duration duration = const Duration(seconds: 1), - }) async { - // TODO: Remove customData in the future release and set variant as required - final snackbarVariant = variant ?? customData; - assert( - snackbarVariant != null, - 'No variant defined, you should provide the variant property to show a custom snackbar', - ); - - final snackbarConfigSupplied = _customSnackbarConfigs[snackbarVariant]; - final snackbarConfigBuilder = - _customSnackbarConfigBuilders[snackbarVariant]; - - final snackbarConfig = snackbarConfigBuilder != null - ? snackbarConfigBuilder() - : snackbarConfigSupplied; - - if (snackbarConfig == null) { - throw CustomSnackbarException( - 'No config found for $snackbarVariant make sure you have called registerCustomConfig with a config or a builder. See [https://pub.dev/packages/stacked_services#custom-styles] for implementation details.', - ); - } - - final mainButtonBuilder = _mainButtonBuilder[variant]; - final hasMainButtonBuilder = mainButtonBuilder != null; - - final mainButtonWidget = hasMainButtonBuilder - ? mainButtonBuilder!(mainButtonTitle, onMainButtonTapped) - : _getMainButtonWidget( - mainButtonTitle: mainButtonTitle, - onMainButtonTapped: onMainButtonTapped, - config: snackbarConfig, - ); - - final getBar = GetSnackBar( - key: Key('snackbar_view'), - titleText: title != null - ? Text( - title, - key: Key('snackbar_text_title'), - style: TextStyle( - color: snackbarConfig.titleColor ?? snackbarConfig.textColor, - fontWeight: FontWeight.w800, - fontSize: 16, - ), - textAlign: snackbarConfig.titleTextAlign, - ) - : null, - messageText: Text( - message, - key: Key('snackbar_text_message'), - style: TextStyle( - color: snackbarConfig.messageColor ?? snackbarConfig.textColor, - fontWeight: FontWeight.w300, - fontSize: 14, - ), - textAlign: snackbarConfig.messageTextAlign, - ), - icon: snackbarConfig.icon, - shouldIconPulse: snackbarConfig.shouldIconPulse, - maxWidth: snackbarConfig.maxWidth, - margin: snackbarConfig.margin ?? EdgeInsets.zero, - padding: snackbarConfig.padding, - borderRadius: snackbarConfig.borderRadius, - borderColor: snackbarConfig.borderColor, - borderWidth: snackbarConfig.borderWidth, - backgroundColor: snackbarConfig.backgroundColor, - leftBarIndicatorColor: snackbarConfig.leftBarIndicatorColor, - boxShadows: snackbarConfig.boxShadows, - backgroundGradient: snackbarConfig.backgroundGradient, - mainButton: mainButtonWidget, - onTap: (object) => onTap!(), - duration: duration, - isDismissible: snackbarConfig.isDismissible, - dismissDirection: snackbarConfig.dismissDirection, - showProgressIndicator: snackbarConfig.showProgressIndicator, - progressIndicatorController: snackbarConfig.progressIndicatorController, - progressIndicatorBackgroundColor: - snackbarConfig.progressIndicatorBackgroundColor, - progressIndicatorValueColor: snackbarConfig.progressIndicatorValueColor, - snackPosition: snackbarConfig.snackPosition, - snackStyle: snackbarConfig.snackStyle, - forwardAnimationCurve: snackbarConfig.forwardAnimationCurve, - reverseAnimationCurve: snackbarConfig.reverseAnimationCurve, - animationDuration: snackbarConfig.animationDuration, - barBlur: snackbarConfig.barBlur, - overlayBlur: snackbarConfig.overlayBlur, - overlayColor: snackbarConfig.overlayColor, - userInputForm: snackbarConfig.userInputForm, - ); - - if (snackbarConfig.instantInit) { - return getBar.show(); - } else { - Completer completer = new Completer(); - WidgetsBinding.instance?.addPostFrameCallback((_) async { - final result = await getBar.show(); - completer.complete(result); - }); - return completer.future; - } - } - - TextButton? _getMainButtonWidget({ - String? mainButtonTitle, - void Function()? onMainButtonTapped, - SnackbarConfig? config, - }) { - if (mainButtonTitle == null) { - return null; - } - - return TextButton( - key: Key('snackbar_touchable_mainButton'), - child: Text( - mainButtonTitle, - key: Key('snackbar_text_mainButtonTitle'), - style: TextStyle( - color: - config?.mainButtonTextColor ?? config?.textColor ?? Colors.white, - ), - ), - onPressed: onMainButtonTapped, - ); - } -} diff --git a/packages/stacked_services/lib/src/stacked_service.dart b/packages/stacked_services/lib/src/stacked_service.dart deleted file mode 100644 index 5058c6f74..000000000 --- a/packages/stacked_services/lib/src/stacked_service.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:stacked_services/src/route_observer.dart'; - -/// This service exposes properties that is required to be set before any of the services can be used -class StackedService { - const StackedService._(); - - /// Returns the [Get.key] value to be set in the applications navigation - static GlobalKey? get navigatorKey => Get.key; - - /// Creates and/or returns a new navigator key based on the index passed in - static GlobalKey? nestedNavigationKey(int index) => - Get.nestedKey(index); - - /// Returns the [GetObserver] to be passed through navigatorObservers in MaterialApp to use all the functionalities - static NavigatorObserver get routeObserver => StackObserver(); -} diff --git a/packages/stacked_services/lib/stacked_services.dart b/packages/stacked_services/lib/stacked_services.dart deleted file mode 100644 index 558a52542..000000000 --- a/packages/stacked_services/lib/stacked_services.dart +++ /dev/null @@ -1,11 +0,0 @@ -library stacked_services; - -export 'package:get/get.dart'; -export 'src/navigation_service.dart'; -export 'src/dialog/dialog_service.dart'; -export 'src/bottom_sheet/bottom_sheet_service.dart'; -export 'src/snackbar/snackbar_service.dart'; -export 'src/snackbar/snackbar_config.dart'; -export 'src/models/overlay_request.dart'; -export 'src/models/overlay_response.dart'; -export 'src/stacked_service.dart'; diff --git a/packages/stacked_services/pubspec.yaml b/packages/stacked_services/pubspec.yaml deleted file mode 100644 index 921c2ccb3..000000000 --- a/packages/stacked_services/pubspec.yaml +++ /dev/null @@ -1,57 +0,0 @@ -name: stacked_services -description: A package that contains some default implementations of services required for a cleaner implementation of the Stacked Architecture. -version: 0.8.17 -homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_services - -environment: - sdk: ">=2.12.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - - # navigation - get: ^4.5.1 - - crypto: ^3.0.1 - -dev_dependencies: - flutter_test: - sdk: flutter - - build_runner: - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stacked_services/test/stacked_services_test.dart b/packages/stacked_services/test/stacked_services_test.dart deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/stacked_themes/.gitignore b/packages/stacked_themes/.gitignore deleted file mode 100644 index e53a16518..000000000 --- a/packages/stacked_themes/.gitignore +++ /dev/null @@ -1,75 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Flutter.podspec -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/Flutter/flutter_export_environment.sh -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/stacked_themes/.metadata b/packages/stacked_themes/.metadata deleted file mode 100644 index 9e93edda9..000000000 --- a/packages/stacked_themes/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 8af6b2f038c1172e61d418869363a28dffec3cb4 - channel: stable - -project_type: package diff --git a/packages/stacked_themes/CHANGELOG.md b/packages/stacked_themes/CHANGELOG.md deleted file mode 100644 index 160289116..000000000 --- a/packages/stacked_themes/CHANGELOG.md +++ /dev/null @@ -1,87 +0,0 @@ -## 0.3.8+1 - -- bumps versions of provider and and status bar package - -## 0.3.8 - -- Adds `navigationBarColorBuilder` to change navigation bar color - -## 0.3.7 - -- Fixed `flutter_statusbarcolor_ns` deprecated Android embedding warning - -## 0.3.6 - -- Replaced `dart:io` with `universal_io` - -## 0.3.5 - -- Fixed `PlatformService` registration issue - -## 0.3.4 - -- Fixed Expception being thrown for non-Android and non-iOS platform - -## 0.3.3 - -- Updated dependencies: - - - rxdart: ^0.26.0 -> ^0.27.1 - - shared_preferences: ^2.0.4 -> ^2.0.6 - - get_it: ^6.0.0 -> ^7.1.3 - - mockito: ^5.0.0-nullsafety.7 -> ^5.0.9 - -## 0.3.2 - -- Makes `ThemeService.getInstance` non-nullable since we'll always construct a theme service in there if null - -## 0.3.1 - -- Adds a `selectedThemeIndex` property to ThemeManager for getting currently enabled theme. - -## 0.3.0 - -- Updates the package to null-safety - -## 0.2.4 - -- provider: ^4.3.3 -> ^5.0.0 -- rxdart: ^0.25.0 -> ^0.26.0 -- shared_preferences: ^0.5.12+4 -> ^2.0.4 -- get_it: ^5.0.6 -> ^6.0.0 - -## 0.2.3 - -- Use get_it newInstance to avoid clashing with the app - -## 0.2.2+2 - -- Updates packages to the latest - -## 0.2.2+1 - -- Updates get it to latest - -## 0.2.2 - -- Re-apply system ThemeMode when we get back from background - -## 0.2.1+1 - -- Fixes bad push - -## 0.2.1 - -- Exposes `ThemeManagerMode` on the `ThemeService` and `ThemeMode` on the `ThemeManager`. - -## 0.2.0 - -- Adds functionality for setting the `ThemeMode` directly using `setThemeMode` function on the `ThemeManager` - -## 0.1.0+1 - -- Adds missing parameter in theme mode - -## 0.1.0 - -- Initial release of Themes functionality diff --git a/packages/stacked_themes/LICENSE b/packages/stacked_themes/LICENSE deleted file mode 100644 index f8adbe441..000000000 --- a/packages/stacked_themes/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Dane Mackier and other contributors - -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: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -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. \ No newline at end of file diff --git a/packages/stacked_themes/README.md b/packages/stacked_themes/README.md deleted file mode 100644 index 3affd548f..000000000 --- a/packages/stacked_themes/README.md +++ /dev/null @@ -1,187 +0,0 @@ -# Stacked Themes - -This package is a set of widgets and classes to help manages multiple themes or Dark/Light theme functionality in a Flutter app. - -## Setup - -Add the stacked_themes package to the pubspec.yaml file - -```yaml -dependencies: - ... - stacked_themes: -``` - -This package provides some basic classes to make theme management easier when building your flutter application. stacked_themes provides you with the basic functionality of swapping out the ThemeData provided to your app, which can be accessed using `Theme.of(context)`. In addition to that it also provides you with an helper function where you can change your status bar color. Lets take a look at the code. - -## Usage - -The package makes use of a `ThemeManager` to manage all the theme functionalities. To start off we have to call the initialise function of the `ThemeManager` before the app is started. Change your main function to return a `Future` and, make it async and await the initialise static function call on `ThemeManager`. - -```dart -Future main() async { - await ThemeManager.initialise(); - runApp(MyApp()); -} -``` - -### Theme Builder - -We start by wrapping our `MaterialApp` or any other `App` with our `ThemeBuilder`. The builder function returns a context, regularTheme, darkTheme and a themeMode. We supply this to the `MaterialApp`. - -```dart -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ThemeBuilder( - builder: (context, regularTheme, darkTheme, themeMode) => MaterialApp( - title: 'Flutter Demo', - theme: regularTheme, - darkTheme: darkTheme, - themeMode: themeMode, - ... - ), - ); - } -} -``` - -This will rebuild your app and pass a new theme through the context for you to use within your application. - -### Supply Multiple Themes - -The `ThemeBuilder` has a property called themes where you can supply a list of `ThemeData`. This is usually only 2 or 3, light, dark, neutral but we’re keeping it a list so you can have as many as you’d like. To keep things clean we will return the list of themes from a function created in a different file. Create a new file called `theme_setup.dart` . You can place it in the UI folders root location or leave it in root of lib. I haven’t decided where it’s the best yet so it's up to you - -```dart -List getThemes() { - return [ - ThemeData(backgroundColor: Colors.blue, accentColor: Colors.yellow), - ThemeData(backgroundColor: Colors.white, accentColor: Colors.green), - ThemeData(backgroundColor: Colors.purple, accentColor: Colors.green), - ThemeData(backgroundColor: Colors.black, accentColor: Colors.red), - ThemeData(backgroundColor: Colors.red, accentColor: Colors.blue), - ]; -} -``` - -This is a pretty simple function but can start to look quite busy given how many of the theme properties you will use in your Themes. Which is why I recommend you keep it in a separate file. Go back to your main.dart file and supply the `getThemes()` value to the `themes` property of the `ThemeBuilder`. - -```dart -Widget build(BuildContext context) { - return ThemeBuilder( - themes: getThemes(), - builder: (context, regularTheme, darkTheme, themeMode) => MaterialApp( - title: 'Flutter Demo', - theme: regularTheme, - darkTheme: darkTheme, - themeMode: themeMode, - ... - ), - ); -} -``` - -The way we swap between themes is by setting the index of the theme we want to use. It will start off with the first theme in the list. To update the theme to any other theme all you have to do in the UI is call `getThemeManager(context)` to get the `ThemeManager` and then call `selectThemeAtIndex(1)` on it. This is the code to select the second theme in the list of themes supplied. - -```dart -getThemeManager(context) - .selectThemeAtIndex(1); -``` - -You can get the currently selected theme index by using the `selectedThemeIndex` property of `ThemeManager`. - -``` -getThemeManager(context).selectedThemeIndex; -``` - -This will return the index of the currently enabled theme. - -### Light and Dark - -If you only want to use Light and Dark themes then you can supply that to the `ThemeBuilder` instead of supplying multiple themes. When supplying Light and Dark themes you can also supply a defaultThemeMode, which is `ThemeMode.system` by default. When you leave it as `system` you're telling the `ThemeManager` that you want the system to determine if you're using light or dark mode. This is how you would supply your dark and light theme. - -```dart -Widget build(BuildContext context) { - return ThemeBuilder( - defaultThemeMode: ThemeMode.light, - darkTheme: ThemeData( - brightness: Brightness.dark, - backgroundColor: Colors.blue[700], - accentColor: Colors.yellow[700], - ), - lightTheme: ThemeData( - brightness: Brightness.light, - backgroundColor: Colors.blue[300], - accentColor: Colors.yellow[300], - ), - builder: (context, regularTheme, darkTheme, themeMode) => MaterialApp( - title: 'Flutter Demo', - theme: regularTheme, - darkTheme: darkTheme, - themeMode: themeMode, - ... - ), - ); -} -``` - -To toggle between light and dark mode you can use the `toggleDarkLightTheme` function on the `ThemeManager`. - -```dart -getThemeManager(context) - .toggleDarkLightTheme(); -``` - -In addition to toggling the theme you can also use the `setThemeMode` function to set the `ThemeMode` on the `ThemeManager`. - -```dart -getThemeManager(context) - .setThemeMode(ThemeMode.light); -``` - -The `ThemeManager` will remember the last set value and broadcast that the next time the `ThemeManager` is constructed. - -### Changing status bar color - -Sometimes you want to status bar color to change as well when you have a specific theme. Most apps leave this transparent to make use of the backgrounds color. If your app requires you to change the status bar color there’s functionality for that as well. The `ThemeBuilder` has a property `statusBarColorBuilder` which is a function that expects you to return a `Color`. It provides you with the theme to be selected and you can use that to return the color you’d like to show for your status bar. - -```dart -Widget build(BuildContext context) { - return ThemeBuilder( - statusBarColorBuilder: (theme) => theme.accentColor, - lightTheme: ThemeData(...), - darkTheme: ThemeData(...), - ... - ); -} -``` - -In the case above we return the accentColor of the theme for the statusbar color. This functionality will automatically check if the status text is visible and will change the text from either white to black depending on the backgrounds value intensity (how close it is to white / black). - -### Theme Persistence - -If any of the themes are changed by the user during the lifetime of the app the theme that was last selected will automatically be applied when the app starts up again. This functionality is created through using the [shared_preferences](https://pub.dev/packages/shared_preferences) package. - -## Using a Theme - -Now that you’ve setup all your theme functionality lets look at how to use it in your UI. The first thing to know is you can access your theme data the normal way. - -```dart -var theme = Theme.of(context); -``` - -This means the rest of your Flutter code still stays the same. - -### Access in Business Logic - -As you know. One of the rules I stick too is that there should be no UI related code or classes making use of the UI directly in any of your business logic. For that reason there is also a `ThemeService` which will allow you to call the same functions required to change between themes. This service should be used as any of the other stacked_services that you’ve used in the past. First we register it with the locator. - -```dart -locator.registerSingleton(() => ThemeService.getInstance()); - -// or add it to your third_party_services_module if you’re using injectable -``` - -Then use it in the `ViewModel` or other services by getting it from the `locator`. And that’s it. That’s all the functionality required. If you’d like to talk more about this please checkout [the slack](https://join.slack.com/t/filledstacks/shared_invite/zt-8hae7yly-vjZX3sW5twN9v7DBlTsgrQ) and ask your questions there. - -If you want to request some additional features or changes don’t hesitate to file an issue. diff --git a/packages/stacked_themes/example/.gitignore b/packages/stacked_themes/example/.gitignore deleted file mode 100644 index fcc0e779d..000000000 --- a/packages/stacked_themes/example/.gitignore +++ /dev/null @@ -1,43 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Web related -lib/generated_plugin_registrant.dart - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Exceptions to above rules. -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/stacked_themes/example/.metadata b/packages/stacked_themes/example/.metadata deleted file mode 100644 index 6ddc6745d..000000000 --- a/packages/stacked_themes/example/.metadata +++ /dev/null @@ -1,10 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: 8af6b2f038c1172e61d418869363a28dffec3cb4 - channel: stable - -project_type: app diff --git a/packages/stacked_themes/example/README.md b/packages/stacked_themes/example/README.md deleted file mode 100644 index 3c4091a67..000000000 --- a/packages/stacked_themes/example/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# themes_example - -A new Flutter project. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) - -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/packages/stacked_themes/example/android/.gitignore b/packages/stacked_themes/example/android/.gitignore deleted file mode 100644 index 0b9e049b0..000000000 --- a/packages/stacked_themes/example/android/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java diff --git a/packages/stacked_themes/example/android/app/build.gradle b/packages/stacked_themes/example/android/app/build.gradle deleted file mode 100644 index 53400255f..000000000 --- a/packages/stacked_themes/example/android/app/build.gradle +++ /dev/null @@ -1,63 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - compileSdkVersion 28 - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - lintOptions { - disable 'InvalidPackage' - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.themes_example" - minSdkVersion 16 - targetSdkVersion 28 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/packages/stacked_themes/example/android/app/src/debug/AndroidManifest.xml b/packages/stacked_themes/example/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 00cbaceb1..000000000 --- a/packages/stacked_themes/example/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/stacked_themes/example/android/app/src/main/AndroidManifest.xml b/packages/stacked_themes/example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 76b859f8b..000000000 --- a/packages/stacked_themes/example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/packages/stacked_themes/example/android/app/src/main/res/drawable/launch_background.xml b/packages/stacked_themes/example/android/app/src/main/res/drawable/launch_background.xml deleted file mode 100644 index 84037589b..000000000 --- a/packages/stacked_themes/example/android/app/src/main/res/drawable/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/packages/stacked_themes/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/stacked_themes/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7..000000000 Binary files a/packages/stacked_themes/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_themes/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/stacked_themes/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79b..000000000 Binary files a/packages/stacked_themes/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_themes/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/stacked_themes/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 09d439148..000000000 Binary files a/packages/stacked_themes/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_themes/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/stacked_themes/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34..000000000 Binary files a/packages/stacked_themes/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_themes/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/stacked_themes/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eeb..000000000 Binary files a/packages/stacked_themes/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/stacked_themes/example/android/app/src/main/res/values/styles.xml b/packages/stacked_themes/example/android/app/src/main/res/values/styles.xml deleted file mode 100644 index a554de436..000000000 --- a/packages/stacked_themes/example/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/packages/stacked_themes/example/android/app/src/profile/AndroidManifest.xml b/packages/stacked_themes/example/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 00cbaceb1..000000000 --- a/packages/stacked_themes/example/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/packages/stacked_themes/example/android/build.gradle b/packages/stacked_themes/example/android/build.gradle deleted file mode 100644 index 7cad15687..000000000 --- a/packages/stacked_themes/example/android/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -buildscript { - ext.kotlin_version = '1.3.50' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - jcenter() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/packages/stacked_themes/example/android/gradle.properties b/packages/stacked_themes/example/android/gradle.properties deleted file mode 100644 index 74c1d924c..000000000 --- a/packages/stacked_themes/example/android/gradle.properties +++ /dev/null @@ -1,4 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M -android.enableR8=true -android.useAndroidX=true -android.enableJetifier=true diff --git a/packages/stacked_themes/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/stacked_themes/example/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 31afd709e..000000000 --- a/packages/stacked_themes/example/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Jun 23 08:50:38 CEST 2017 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/packages/stacked_themes/example/android/settings.gradle b/packages/stacked_themes/example/android/settings.gradle deleted file mode 100644 index c47688cdc..000000000 --- a/packages/stacked_themes/example/android/settings.gradle +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -include ':app' - -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() - -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } - -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/stacked_themes/example/ios/.gitignore b/packages/stacked_themes/example/ios/.gitignore deleted file mode 100644 index 0f1df0fdd..000000000 --- a/packages/stacked_themes/example/ios/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -*.mode1v3 -*.mode2v3 -*.moved-aside -*.pbxuser -*.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!default.mode1v3 -!default.mode2v3 -!default.pbxuser -!default.perspectivev3 diff --git a/packages/stacked_themes/example/ios/Flutter/.last_build_id b/packages/stacked_themes/example/ios/Flutter/.last_build_id deleted file mode 100644 index df7e1f35d..000000000 --- a/packages/stacked_themes/example/ios/Flutter/.last_build_id +++ /dev/null @@ -1 +0,0 @@ -f669b27739b8c96df2988f18732a08e7 \ No newline at end of file diff --git a/packages/stacked_themes/example/ios/Flutter/AppFrameworkInfo.plist b/packages/stacked_themes/example/ios/Flutter/AppFrameworkInfo.plist deleted file mode 100644 index 58e65f9b5..000000000 --- a/packages/stacked_themes/example/ios/Flutter/AppFrameworkInfo.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 8.0 - - diff --git a/packages/stacked_themes/example/ios/Podfile.lock b/packages/stacked_themes/example/ios/Podfile.lock deleted file mode 100644 index f9d4efdf5..000000000 --- a/packages/stacked_themes/example/ios/Podfile.lock +++ /dev/null @@ -1,28 +0,0 @@ -PODS: - - Flutter (1.0.0) - - flutter_statusbarcolor (0.0.1): - - Flutter - - shared_preferences (0.0.1): - - Flutter - -DEPENDENCIES: - - Flutter (from `Flutter`) - - flutter_statusbarcolor (from `.symlinks/plugins/flutter_statusbarcolor/ios`) - - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - -EXTERNAL SOURCES: - Flutter: - :path: Flutter - flutter_statusbarcolor: - :path: ".symlinks/plugins/flutter_statusbarcolor/ios" - shared_preferences: - :path: ".symlinks/plugins/shared_preferences/ios" - -SPEC CHECKSUMS: - Flutter: 0e3d915762c693b495b44d77113d4970485de6ec - flutter_statusbarcolor: 8afb1071ed8c58be929f1e32b36c629771fd2259 - shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d - -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c - -COCOAPODS: 1.9.3 diff --git a/packages/stacked_themes/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/stacked_themes/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 59c6d3946..000000000 --- a/packages/stacked_themes/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/packages/stacked_themes/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/stacked_themes/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme deleted file mode 100644 index be0b92eff..000000000 --- a/packages/stacked_themes/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/stacked_themes/example/ios/Runner/AppDelegate.swift b/packages/stacked_themes/example/ios/Runner/AppDelegate.swift deleted file mode 100644 index 376368379..000000000 --- a/packages/stacked_themes/example/ios/Runner/AppDelegate.swift +++ /dev/null @@ -1,13 +0,0 @@ -import UIKit -import Flutter - -@UIApplicationMain -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 1950fd80e..000000000 --- a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dc9ada472..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 28c6bf030..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 2ccbfd967..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index f091b6b0b..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cde12118..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e7e..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index dcdc2306c..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 2ccbfd967..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8f5..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b8609..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b8609..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d164a..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d39..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 6a84f41e1..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index d0e1f5853..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json deleted file mode 100644 index d08a4de32..000000000 --- a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "LaunchImage.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LaunchImage@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png deleted file mode 100644 index 9da19eaca..000000000 Binary files a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and /dev/null differ diff --git a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md deleted file mode 100644 index 65a94b5db..000000000 --- a/packages/stacked_themes/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - -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. \ No newline at end of file diff --git a/packages/stacked_themes/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/stacked_themes/example/ios/Runner/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 497371ea2..000000000 --- a/packages/stacked_themes/example/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/stacked_themes/example/ios/Runner/Base.lproj/Main.storyboard b/packages/stacked_themes/example/ios/Runner/Base.lproj/Main.storyboard deleted file mode 100644 index bbb83caae..000000000 --- a/packages/stacked_themes/example/ios/Runner/Base.lproj/Main.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/stacked_themes/example/ios/Runner/Info.plist b/packages/stacked_themes/example/ios/Runner/Info.plist deleted file mode 100644 index d612b5133..000000000 --- a/packages/stacked_themes/example/ios/Runner/Info.plist +++ /dev/null @@ -1,45 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - themes_example - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - - diff --git a/packages/stacked_themes/example/ios/Runner/Runner-Bridging-Header.h b/packages/stacked_themes/example/ios/Runner/Runner-Bridging-Header.h deleted file mode 100644 index fae207f9e..000000000 --- a/packages/stacked_themes/example/ios/Runner/Runner-Bridging-Header.h +++ /dev/null @@ -1 +0,0 @@ -#import "GeneratedPluginRegistrant.h" diff --git a/packages/stacked_themes/example/lib/app/locator.dart b/packages/stacked_themes/example/lib/app/locator.dart deleted file mode 100644 index 43894ce75..000000000 --- a/packages/stacked_themes/example/lib/app/locator.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:get_it/get_it.dart'; -import 'package:stacked_themes/stacked_themes.dart'; - -final GetIt locator = GetIt.instance; - -void setupLocator() { - locator.registerSingleton(ThemeService.getInstance()); -} diff --git a/packages/stacked_themes/example/lib/main.dart b/packages/stacked_themes/example/lib/main.dart deleted file mode 100644 index 267df999d..000000000 --- a/packages/stacked_themes/example/lib/main.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:stacked_themes/stacked_themes.dart'; -import 'package:themes_example/app/locator.dart'; -import 'package:themes_example/ui/dark_light/dark_light_view.dart'; -import 'package:themes_example/ui/multiple_themes/multiple_themes_view.dart'; -import 'package:themes_example/ui/theme_setup.dart'; - -Future main() async { - setupLocator(); - await ThemeManager.initialise(); - runApp(MyApp()); -} - -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return ThemeBuilder( - defaultThemeMode: ThemeMode.system, - darkTheme: ThemeData( - brightness: Brightness.dark, - backgroundColor: Colors.blue[700], - accentColor: Colors.yellow[700], - ), - lightTheme: ThemeData( - brightness: Brightness.light, - backgroundColor: Colors.blue[300], - accentColor: Colors.yellow[300], - ), - statusBarColorBuilder: (theme) => theme.accentColor, - navigationBarColorBuilder: (theme) => theme.accentColor, - // themes: getThemes(), - builder: (context, regularTheme, darkTheme, themeMode) => MaterialApp( - title: 'Flutter Demo', - theme: regularTheme, - darkTheme: darkTheme, - themeMode: themeMode, - home: DarkLightView(), - ), - ); - } -} diff --git a/packages/stacked_themes/example/lib/ui/dark_light/dark_light_view.dart b/packages/stacked_themes/example/lib/ui/dark_light/dark_light_view.dart deleted file mode 100644 index a8542db3d..000000000 --- a/packages/stacked_themes/example/lib/ui/dark_light/dark_light_view.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; -import 'package:stacked_themes/stacked_themes.dart'; - -import 'dark_light_viewmodel.dart'; - -class DarkLightView extends StatelessWidget { - const DarkLightView({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ViewModelBuilder.reactive( - builder: (context, model, child) => Scaffold( - backgroundColor: Theme.of(context).backgroundColor, - body: Padding( - padding: const EdgeInsets.all(20.0), - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'This adjusts Dark/Light depending on the System brightness setting and if you set themeMode to system in the main file.', - textAlign: TextAlign.center, - ), - SizedBox( - height: 30, - ), - GestureDetector( - onTap: () { - var themeManger = getThemeManager(context); - themeManger.toggleDarkLightTheme(); - }, - child: Text( - 'Tapping this toggles Dark/Light theme', - textAlign: TextAlign.center, - ), - ), - SizedBox( - height: 30, - ), - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - MaterialButton( - child: Text('Dark'), - color: Colors.grey, - onPressed: () { - var themeManger = getThemeManager(context); - themeManger.setThemeMode(ThemeMode.dark); - }, - ), - MaterialButton( - child: Text('Light'), - color: Colors.grey, - onPressed: () { - var themeManger = getThemeManager(context); - themeManger.setThemeMode(ThemeMode.light); - }, - ), - MaterialButton( - child: Text('System'), - color: Colors.grey, - onPressed: () { - var themeManger = getThemeManager(context); - themeManger.setThemeMode(ThemeMode.system); - }, - ), - ], - ) - ], - ), - ), - ), - ), - viewModelBuilder: () => DarkLightViewModel(), - ); - } -} diff --git a/packages/stacked_themes/example/lib/ui/dark_light/dark_light_viewmodel.dart b/packages/stacked_themes/example/lib/ui/dark_light/dark_light_viewmodel.dart deleted file mode 100644 index 59a123a72..000000000 --- a/packages/stacked_themes/example/lib/ui/dark_light/dark_light_viewmodel.dart +++ /dev/null @@ -1,3 +0,0 @@ -import 'package:stacked/stacked.dart'; - -class DarkLightViewModel extends BaseViewModel {} diff --git a/packages/stacked_themes/example/lib/ui/multiple_themes/multiple_themes_view.dart b/packages/stacked_themes/example/lib/ui/multiple_themes/multiple_themes_view.dart deleted file mode 100644 index fa4d4bd44..000000000 --- a/packages/stacked_themes/example/lib/ui/multiple_themes/multiple_themes_view.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stacked/stacked.dart'; -import 'package:stacked_themes/stacked_themes.dart'; - -import 'multiple_themes_viewmodel.dart'; - -class MultipleThemesView extends StatelessWidget { - const MultipleThemesView({Key key}) : super(key: key); - - @override - Widget build(BuildContext context) { - var theme = Theme.of(context); - return ViewModelBuilder.reactive( - builder: (context, model, child) => Scaffold( - backgroundColor: theme.backgroundColor, - body: Center( - child: Wrap( - spacing: 30, - runSpacing: 20, - alignment: WrapAlignment.start, - direction: Axis.horizontal, - children: model.themes - .map( - (themeData) => GestureDetector( - onTap: () => model.setTheme(themeData), - child: Container( - width: 80, - padding: const EdgeInsets.symmetric( - horizontal: 5, vertical: 10), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - color: Colors.grey[100]), - child: Text(themeData.title), - ), - ), - ) - .toList(), - ), - ), - ), - viewModelBuilder: () => MultipleThemesViewModel(), - ); - } -} diff --git a/packages/stacked_themes/example/lib/ui/multiple_themes/multiple_themes_viewmodel.dart b/packages/stacked_themes/example/lib/ui/multiple_themes/multiple_themes_viewmodel.dart deleted file mode 100644 index 3b9b21a55..000000000 --- a/packages/stacked_themes/example/lib/ui/multiple_themes/multiple_themes_viewmodel.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:stacked/stacked.dart'; -import 'package:stacked_themes/stacked_themes.dart'; -import 'package:themes_example/app/locator.dart'; - -class ThemeModel { - final int index; - final String title; - - ThemeModel({this.index, this.title}); -} - -class MultipleThemesViewModel extends BaseViewModel { - final ThemeService _themeService = locator(); - - List get themes => List.generate( - 5, - (index) => ThemeModel( - index: index, - title: _getTitleForIndex(index), - )); - - String _getTitleForIndex(int index) { - switch (index) { - case 0: - return 'Blue and Yellow'; - case 1: - return 'Green and White'; - case 2: - return 'Purple and Green'; - case 3: - return 'Black and Red'; - case 4: - return 'Red and Blue'; - } - - return 'No theme for index'; - } - - void setTheme(ThemeModel themeData) => - _themeService.selectThemeAtIndex(themeData.index); -} diff --git a/packages/stacked_themes/example/lib/ui/theme_setup.dart b/packages/stacked_themes/example/lib/ui/theme_setup.dart deleted file mode 100644 index b6ddf4a4b..000000000 --- a/packages/stacked_themes/example/lib/ui/theme_setup.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter/material.dart'; - -List getThemes() { - return [ - ThemeData(backgroundColor: Colors.blue, accentColor: Colors.yellow), - ThemeData(backgroundColor: Colors.white, accentColor: Colors.green), - ThemeData(backgroundColor: Colors.purple, accentColor: Colors.green), - ThemeData(backgroundColor: Colors.black, accentColor: Colors.red), - ThemeData(backgroundColor: Colors.red, accentColor: Colors.blue), - ]; -} diff --git a/packages/stacked_themes/example/pubspec.yaml b/packages/stacked_themes/example/pubspec.yaml deleted file mode 100644 index 83caeb7f9..000000000 --- a/packages/stacked_themes/example/pubspec.yaml +++ /dev/null @@ -1,78 +0,0 @@ -name: themes_example -description: A new Flutter project. - -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. -publish_to: "none" # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+1 - -environment: - sdk: ">=2.7.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.3 - stacked: - stacked_themes: - path: ../ - get_it: - -dev_dependencies: - flutter_test: - sdk: flutter - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stacked_themes/lib/src/locator_setup.dart b/packages/stacked_themes/lib/src/locator_setup.dart deleted file mode 100644 index 0933244aa..000000000 --- a/packages/stacked_themes/lib/src/locator_setup.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:get_it/get_it.dart'; -import 'package:stacked_themes/src/services/platform_service.dart'; -import 'package:stacked_themes/src/services/shared_preferences_service.dart'; -import 'package:stacked_themes/src/services/statusbar_service.dart'; - -final locator = GetIt.asNewInstance(); - -Future setupLocator() async { - SharedPreferencesService sharedPreferences = - await SharedPreferencesService.getInstance(); - locator.registerSingleton(sharedPreferences); - - locator.registerLazySingleton(() => StatusBarService()); - locator.registerLazySingleton(() => PlatformService()); -} diff --git a/packages/stacked_themes/lib/src/services/platform_service.dart b/packages/stacked_themes/lib/src/services/platform_service.dart deleted file mode 100644 index c9458463a..000000000 --- a/packages/stacked_themes/lib/src/services/platform_service.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:universal_io/io.dart'; - -/// An abstraction over the Platform information so that we can have pure unit tests -class PlatformService { - bool get isIos => Platform.isIOS; - bool get isAndroid => Platform.isAndroid; - - bool get isMobilePlatform => isIos || isAndroid; -} diff --git a/packages/stacked_themes/lib/src/services/shared_preferences_service.dart b/packages/stacked_themes/lib/src/services/shared_preferences_service.dart deleted file mode 100644 index fb0381d5d..000000000 --- a/packages/stacked_themes/lib/src/services/shared_preferences_service.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -class SharedPreferencesService { - static SharedPreferencesService? _instance; - - static Future getInstance() async { - if (_instance == null) { - _instance = - SharedPreferencesService._(await SharedPreferences.getInstance()); - } - return _instance!; - } - - final SharedPreferences _preferences; - SharedPreferencesService._(this._preferences); - - static const _ThemeIndexKey = 'user_key'; - static const _UserThemeModeKey = 'user_theme_mode_key'; - - int? get themeIndex => _getFromDisk(_ThemeIndexKey); - - set themeIndex(int? value) => _saveToDisk(_ThemeIndexKey, value); - - ThemeMode? get userThemeMode { - var userThemeString = _getFromDisk(_UserThemeModeKey); - if (userThemeString == ThemeMode.dark.toString()) { - return ThemeMode.dark; - } - - if (userThemeString == ThemeMode.light.toString()) { - return ThemeMode.light; - } - - return null; - } - - set userThemeMode(ThemeMode? value) { - if (value == null) { - _saveToDisk(_UserThemeModeKey, value); - } else { - var userTheme = value.toString(); - _saveToDisk(_UserThemeModeKey, userTheme); - } - } - - void clearPreferences() { - _preferences.clear(); - } - - dynamic _getFromDisk(String key) { - var value = _preferences.get(key); - return value; - } - - void _saveToDisk(String key, dynamic content) { - if (content is String) { - _preferences.setString(key, content); - } - if (content is bool) { - _preferences.setBool(key, content); - } - if (content is int) { - _preferences.setInt(key, content); - } - if (content is double) { - _preferences.setDouble(key, content); - } - if (content is List) { - _preferences.setStringList(key, content); - } - } -} diff --git a/packages/stacked_themes/lib/src/services/statusbar_service.dart b/packages/stacked_themes/lib/src/services/statusbar_service.dart deleted file mode 100644 index 6687ccc9f..000000000 --- a/packages/stacked_themes/lib/src/services/statusbar_service.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter_statusbarcolor_ns/flutter_statusbarcolor_ns.dart'; - -/// A service dedicated to changing the color of the status bar -class StatusBarService { - Future? updateStatusBarColor(Color statusBarColor) async { - // Set status bar color - await FlutterStatusbarcolor.setStatusBarColor(statusBarColor); - - // Check the constrast between the colors and set the status bar icons colors to white or dark - if (useWhiteForeground(statusBarColor)) { - FlutterStatusbarcolor.setStatusBarWhiteForeground(true); - } else { - FlutterStatusbarcolor.setStatusBarWhiteForeground(false); - } - } - - Future? updateNavigationBarColor(Color navigationBarColor) async { - // Set navigation bar color - await FlutterStatusbarcolor.setNavigationBarColor(navigationBarColor); - - // Check the constrast between the colors and set the navigation bar icons colors to white or dark - if (useWhiteForeground(navigationBarColor)) { - FlutterStatusbarcolor.setNavigationBarWhiteForeground(true); - } else { - FlutterStatusbarcolor.setNavigationBarWhiteForeground(false); - } - } -} diff --git a/packages/stacked_themes/lib/src/theme_builder.dart b/packages/stacked_themes/lib/src/theme_builder.dart deleted file mode 100644 index d986e3927..000000000 --- a/packages/stacked_themes/lib/src/theme_builder.dart +++ /dev/null @@ -1,133 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:stacked_themes/src/theme_manager.dart'; - -import '../stacked_themes.dart'; - -/// A widget that rebuilds itself with a new theme -class ThemeBuilder extends StatefulWidget { - final Widget Function(BuildContext, ThemeData?, ThemeData?, ThemeMode?) - builder; - final List? themes; - final ThemeData? lightTheme; - final ThemeData? darkTheme; - final Color? Function(ThemeData?)? statusBarColorBuilder; - final Color? Function(ThemeData?)? navigationBarColorBuilder; - final ThemeMode defaultThemeMode; - - ThemeBuilder({ - Key? key, - required this.builder, - this.themes, - this.lightTheme, - this.darkTheme, - this.statusBarColorBuilder, - this.navigationBarColorBuilder, - this.defaultThemeMode = ThemeMode.system, - }) : super(key: key); - - @override - _ThemeBuilderState createState() => _ThemeBuilderState( - ThemeManager( - themes: themes, - statusBarColorBuilder: statusBarColorBuilder, - navigationBarColorBuilder: navigationBarColorBuilder, - darkTheme: darkTheme, - lightTheme: lightTheme, - defaultTheme: defaultThemeMode, - ), - ); -} - -class _ThemeBuilderState extends State - with WidgetsBindingObserver { - final ThemeManager themeManager; - - _ThemeBuilderState(this.themeManager); - - @override - Widget build(BuildContext context) { - return Provider.value( - value: themeManager, - builder: (context, child) => StreamProvider( - lazy: false, - initialData: themeManager.initalTheme, - create: (context) => themeManager.themesStream, - builder: (context, child) => Consumer( - child: child, - builder: (context, themeModel, child) => widget.builder( - context, - themeModel.selectedTheme, - themeModel.darkTheme, - themeModel.themeMode, - ), - ), - ), - ); - } - - // Get all services - // final themeService = locator(); - // @override - // Widget build(BuildContext context) { - // return widget.child; - // } - - @override - void initState() { - super.initState(); - WidgetsBinding.instance!.addObserver(this); - } - - @override - void dispose() { - super.dispose(); - WidgetsBinding.instance!.removeObserver(this); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - super.didChangeAppLifecycleState(state); - - switch (state) { - case AppLifecycleState.inactive: - break; - case AppLifecycleState.resumed: - adjustSystemThemeIfNecessary(); - break; - case AppLifecycleState.paused: - break; - case AppLifecycleState.detached: - break; - } - } - - // Should update theme whenever platform brighteness changes. - // This makes sure that theme changes even if the brighteness changes from notification bar. - @override - void didChangePlatformBrightness() { - super.didChangePlatformBrightness(); - adjustSystemThemeIfNecessary(); - } - - //NOTE: re-apply the appropriate theme when the application gets back into the foreground - void adjustSystemThemeIfNecessary() { - switch (themeManager.selectedThemeMode) { - // When app becomes inactive the overlay colors might change. - // Therefore when the app is resumed we also need to update - // overlay colors back to their original state. In case - // selected theme mode is system the overlay colors will be - // automatically updated. - case ThemeMode.light: - case ThemeMode.dark: - final selectedTheme = themeManager.getSelectedTheme().selectedTheme; - themeManager.updateOverlayColors(selectedTheme); - break; - //reapply theme - case ThemeMode.system: - themeManager.setThemeMode(ThemeMode.system); - break; - default: - } - } -} diff --git a/packages/stacked_themes/lib/src/theme_manager.dart b/packages/stacked_themes/lib/src/theme_manager.dart deleted file mode 100644 index c60dc77ed..000000000 --- a/packages/stacked_themes/lib/src/theme_manager.dart +++ /dev/null @@ -1,248 +0,0 @@ -import 'dart:async'; -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:provider/provider.dart'; -import 'package:rxdart/rxdart.dart'; -import 'package:stacked_themes/src/locator_setup.dart'; -import 'package:stacked_themes/src/services/platform_service.dart'; -import 'package:stacked_themes/src/services/shared_preferences_service.dart'; -import 'package:stacked_themes/src/services/statusbar_service.dart'; -import 'package:stacked_themes/src/theme_service.dart'; -import 'package:stacked_themes/stacked_themes.dart'; - -const String SelectedTheme = 'selected-theme'; -const String DarkTheme = 'dark-theme'; - -/// Provides functionality to manage the current theme for the application -class ThemeManager { - final SharedPreferencesService _sharedPreferences = - locator(); - - final StatusBarService _statusBarService = locator(); - final PlatformService _platformService = locator(); - - /// Has to be called before we make use of the theme manager - static Future initialise() async { - WidgetsFlutterBinding.ensureInitialized(); - await setupLocator(); - } - - /// A list of themes that the application can swap to - final List? themes; - - /// The theme to be used when not using the darkTheme - final ThemeData? lightTheme; - - /// The theme to be used when not using the lightTheme - final ThemeData? darkTheme; - - /// The default theme mode to use for the application when the application is frst used. - /// - /// When using system all previously user selected theme will be cleared in favor of the system. - final ThemeMode defaultTheme; - - ThemeMode? _selectedThemeMode; - - ThemeModel? _initialTheme; - - /// Returns the current selected theme mode - ThemeMode? get selectedThemeMode => _selectedThemeMode; - - ThemeModel get initalTheme => _initialTheme!; - - /// A builder function that provides you with the new selected theme that expects you to - /// return a color for the status bar. - final Color? Function(ThemeData?)? statusBarColorBuilder; - - /// A builder function that provides you with the new selected theme that expects you to - /// return a color for the navigation bar. - final Color? Function(ThemeData?)? navigationBarColorBuilder; - - late BehaviorSubject _themesController; - - Stream get themesStream => _themesController.stream; - - /// Returns true if the ThemeMode is dark. This does not apply when you're using system as ThemeMode - bool get isDarkMode => _selectedThemeMode == ThemeMode.dark; - - /// Get currently selected theme - int? get selectedThemeIndex { - if (themes != null && themes!.isNotEmpty) { - int? themeIndex = _sharedPreferences.themeIndex; - return themeIndex == null ? 0 : themeIndex; - } - return null; - } - - ThemeManager({ - this.themes, - this.statusBarColorBuilder, - this.navigationBarColorBuilder, - this.darkTheme, - this.lightTheme, - this.defaultTheme = ThemeMode.system, - }) { - var hasMultipleThemes = themes != null && themes!.length > 1; - var hasLightAndDarkThemes = darkTheme != null && lightTheme != null; - assert(hasMultipleThemes || hasLightAndDarkThemes, - '''You have to supply themes if you want to use themes. You have supplied no themes. Don\'t do that. Supply themes. -You can supply either a list of ThemeData objects to the themes property or a lightTheme and a darkTheme to be swapped between. - '''); - - var storedThemeIndex = _sharedPreferences.themeIndex; - - ThemeData? selectedTheme; - - if (hasMultipleThemes) { - if (storedThemeIndex != null) { - try { - selectedTheme = themes![storedThemeIndex]; - } catch (e) { - print( - '''WARNING: You have changed your number of themes. Because of this we will clear your previously selected - theme and broadcast the first theme in your list of themes.'''); - _sharedPreferences.themeIndex = null; - selectedTheme = themes!.first; - } - } else { - selectedTheme = themes!.first; - } - updateOverlayColors(selectedTheme); - } else { - _selectedThemeMode = defaultTheme; - - var savedUserThemeMode = _sharedPreferences.userThemeMode; - if (savedUserThemeMode != null) { - _selectedThemeMode = savedUserThemeMode; - } - - if (_selectedThemeMode == ThemeMode.system) { - final brighteness = - SchedulerBinding.instance?.window.platformBrightness; - selectedTheme = brighteness == Brightness.dark ? darkTheme : lightTheme; - } else { - selectedTheme = - _selectedThemeMode == ThemeMode.dark ? darkTheme : lightTheme; - } - - updateOverlayColors(selectedTheme); - } - - ThemeModel _currTheme = ThemeModel( - selectedTheme: selectedTheme, - darkTheme: darkTheme, - themeMode: _selectedThemeMode); - - _themesController = BehaviorSubject.seeded(_currTheme); - _initialTheme = _currTheme; - - ThemeService.getInstance().setThemeManager(this); - } - - ThemeModel getSelectedTheme() { - var selectedTheme = - _selectedThemeMode == ThemeMode.dark ? darkTheme : lightTheme; - return ThemeModel( - selectedTheme: selectedTheme, - darkTheme: darkTheme, - themeMode: _selectedThemeMode); - } - - /// Sets the theme for the application equal to the theme at the index - /// in the list of [themes] supplied to the [ThemeBuilder] - Future selectThemeAtIndex(int themeIndex) async { - if (themes == null || themes!.isEmpty) { - throw Exception( - 'You cannot select the theme if you have no themes supplied. Supply a list of themes to the constructor of the ThemeManager if you want to use this function.'); - } - - var theme = themes![themeIndex]; - await updateOverlayColors(theme); - - _themesController.add(ThemeModel( - selectedTheme: theme, - darkTheme: darkTheme, - themeMode: _selectedThemeMode, - )); - - _sharedPreferences.themeIndex = themeIndex; - } - - Future _applyStatusBarColor(ThemeData? theme) async { - if (_platformService.isMobilePlatform) { - var statusBarColor = statusBarColorBuilder?.call(theme); - if (statusBarColor != null) { - await _statusBarService.updateStatusBarColor(statusBarColor); - } - } - } - - Future _applyNavigationBarColor(ThemeData? theme) async { - // Change color only when not on web - if (_platformService.isMobilePlatform) { - var navigationBarColor = navigationBarColorBuilder?.call(theme); - if (navigationBarColor != null) { - await _statusBarService.updateNavigationBarColor(navigationBarColor); - } - } - } - - Future updateOverlayColors(ThemeData? theme) async { - _applyStatusBarColor(theme); - _applyNavigationBarColor(theme); - } - - /// Swaps between the light and dark ThemeMode - void toggleDarkLightTheme() { - _selectedThemeMode = - _selectedThemeMode == ThemeMode.dark ? ThemeMode.light : ThemeMode.dark; - - updateOverlayColors( - _selectedThemeMode == ThemeMode.dark ? darkTheme : lightTheme); - _themesController.add(ThemeModel( - selectedTheme: lightTheme, - darkTheme: darkTheme, - themeMode: _selectedThemeMode, - )); - } - - void setThemeMode(ThemeMode themeMode) { - _selectedThemeMode = themeMode; - - _sharedPreferences.userThemeMode = themeMode; - - if (themeMode != ThemeMode.system) { - updateOverlayColors( - _selectedThemeMode == ThemeMode.dark ? darkTheme : lightTheme); - } else { - var currentBrightness = - SchedulerBinding.instance!.window.platformBrightness; - updateOverlayColors( - currentBrightness == Brightness.dark ? darkTheme : lightTheme); - } - - _themesController.add(ThemeModel( - selectedTheme: lightTheme, - darkTheme: darkTheme, - themeMode: _selectedThemeMode, - )); - } -} - -/// Returns the [ThemeManger] that -ThemeManager getThemeManager(BuildContext context) => - Provider.of(context, listen: false); - -class ThemeModel { - final ThemeData? selectedTheme; - final ThemeData? darkTheme; - final ThemeMode? themeMode; - - ThemeModel({ - required this.selectedTheme, - required this.darkTheme, - required this.themeMode, - }); -} diff --git a/packages/stacked_themes/lib/src/theme_service.dart b/packages/stacked_themes/lib/src/theme_service.dart deleted file mode 100644 index 646705751..000000000 --- a/packages/stacked_themes/lib/src/theme_service.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stacked_themes/src/theme_manager.dart'; - -/// An enum that has a 1 to 1 mapping to ThemeMode.light, dark and system. -/// -/// This is for use in a ViewModel where there should be no reference to Flutter UI -/// code like ThemeMode. -enum ThemeManagerMode { light, dark, system } - -/// A service accessible outside of the UI to allow the control of the [ThemeManager] -class ThemeService { - static ThemeService? _instance; - static ThemeService getInstance() { - if (_instance == null) { - _instance = ThemeService(); - } - - return _instance!; - } - - late ThemeManager _themeManager; - - /// Sets the theme manager instance that will be used from the service - void setThemeManager(ThemeManager manager) => _themeManager = manager; - - /// Sets the theme for the application equal to the theme at the index - /// in the list of [themes] supplied to the [ThemeBuilder] - Future selectThemeAtIndex(int themeIndex) => - _themeManager.selectThemeAtIndex(themeIndex); - - /// Swaps between the light and dark ThemeMode if the defaultThemeMode supplied - /// to the ThemeBuilder is not [ThemeMode.system] - void toggleDarkLightTheme() => _themeManager.toggleDarkLightTheme(); - - /// Returns the number of themes supplied to the [ThemeBuilder] - int get themeCount => _themeManager.themes?.length ?? 0; - - /// Returns true if the ThemeMode is dark. This does not apply when you're using system as ThemeMode - bool get isDarkMode => _themeManager.isDarkMode; - - /// Sets the theme mode on the [ThemeManager] - void setThemeMode(ThemeManagerMode themeManagerMode) => - _themeManager.setThemeMode(_getThemeMode(themeManagerMode)); - - /// Returns the selected theme mode - ThemeManagerMode get selectedThemeMode => - _getThemeManagerMode(_themeManager.selectedThemeMode); - - ThemeMode _getThemeMode(ThemeManagerMode mode) { - switch (mode) { - case ThemeManagerMode.dark: - return ThemeMode.dark; - case ThemeManagerMode.light: - return ThemeMode.light; - case ThemeManagerMode.system: - return ThemeMode.system; - } - } - - ThemeManagerMode _getThemeManagerMode(ThemeMode? mode) { - if (mode != null) - switch (mode) { - case ThemeMode.dark: - return ThemeManagerMode.dark; - case ThemeMode.light: - return ThemeManagerMode.light; - case ThemeMode.system: - return ThemeManagerMode.system; - } - return ThemeManagerMode.system; - } -} diff --git a/packages/stacked_themes/lib/stacked_themes.dart b/packages/stacked_themes/lib/stacked_themes.dart deleted file mode 100644 index 8c37e5a2a..000000000 --- a/packages/stacked_themes/lib/stacked_themes.dart +++ /dev/null @@ -1,5 +0,0 @@ -library stacked_themes; - -export 'src/theme_builder.dart'; -export 'src/theme_manager.dart'; -export 'src/theme_service.dart'; diff --git a/packages/stacked_themes/pubspec.yaml b/packages/stacked_themes/pubspec.yaml deleted file mode 100644 index def514bdf..000000000 --- a/packages/stacked_themes/pubspec.yaml +++ /dev/null @@ -1,64 +0,0 @@ -name: stacked_themes -description: A set of classes to help you better manage Themes in flutter -version: 0.3.8+1 -homepage: https://github.com/FilledStacks/stacked/tree/master/packages/stacked_themes - -environment: - sdk: ">=2.12.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - - provider: ^6.0.0 - rxdart: ^0.27.1 - shared_preferences: ^2.0.6 - get_it: ^7.1.3 - flutter_statusbarcolor_ns: ^0.4.0 - - #universal_io - universal_io: ^2.0.4 - -dev_dependencies: - flutter_test: - sdk: flutter - - mockito: ^5.0.9 - build_runner: - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stacked_themes/test/test_setup.dart b/packages/stacked_themes/test/test_setup.dart deleted file mode 100644 index 690b2024a..000000000 --- a/packages/stacked_themes/test/test_setup.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:stacked_themes/src/locator_setup.dart'; -import 'package:stacked_themes/src/services/platform_service.dart'; -import 'package:stacked_themes/src/services/shared_preferences_service.dart'; -import 'package:stacked_themes/src/services/statusbar_service.dart'; - -import 'test_setup.mocks.dart'; - -@GenerateMocks([], customMocks: [ - MockSpec(returnNullOnMissingStub: true), - MockSpec(returnNullOnMissingStub: true), - MockSpec(returnNullOnMissingStub: true) -]) -SharedPreferencesService getAndRegisterSharedPreferencesServiceMock( - {int? themeIndex, ThemeMode? userThemeMode}) { - _removeRegistrationIfExists(); - var service = MockSharedPreferencesService(); - - when(service.themeIndex).thenReturn(themeIndex); - when(service.userThemeMode).thenReturn(userThemeMode); - - locator.registerSingleton(service); - return service; -} - -StatusBarService getAndRegisterStatusBarServiceMock() { - _removeRegistrationIfExists(); - var service = MockStatusBarService(); - locator.registerSingleton(service); - return service; -} - -PlatformService getAndRegisterPlatformService() { - _removeRegistrationIfExists(); - final service = MockPlatformService(); - - when(service.isMobilePlatform).thenReturn(true); - - locator.registerSingleton(service); - return service; -} - -// Call this before any service registration helper. This is to ensure that if there -// is a service registered we remove it first. We register all services to remove boiler plate from tests -void _removeRegistrationIfExists() { - if (locator.isRegistered()) { - locator.unregister(); - } -} - -void registerServices() { - getAndRegisterSharedPreferencesServiceMock(); - getAndRegisterStatusBarServiceMock(); - getAndRegisterPlatformService(); -} - -void unregisterServices() { - locator.unregister(); - locator.unregister(); - locator.unregister(); -} diff --git a/packages/stacked_themes/test/test_setup.mocks.dart b/packages/stacked_themes/test/test_setup.mocks.dart deleted file mode 100644 index cb02e3c9a..000000000 --- a/packages/stacked_themes/test/test_setup.mocks.dart +++ /dev/null @@ -1,67 +0,0 @@ -// Mocks generated by Mockito 5.0.9 from annotations -// in stacked_themes/test/test_setup.dart. -// Do not manually edit this file. - -import 'dart:async' as _i5; -import 'dart:ui' as _i6; - -import 'package:flutter/src/material/app.dart' as _i3; -import 'package:mockito/mockito.dart' as _i1; -import 'package:stacked_themes/src/services/platform_service.dart' as _i7; -import 'package:stacked_themes/src/services/shared_preferences_service.dart' - as _i2; -import 'package:stacked_themes/src/services/statusbar_service.dart' as _i4; - -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: comment_references -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis - -/// A class which mocks [SharedPreferencesService]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockSharedPreferencesService extends _i1.Mock - implements _i2.SharedPreferencesService { - @override - set themeIndex(int? value) => - super.noSuchMethod(Invocation.setter(#themeIndex, value), - returnValueForMissingStub: null); - @override - set userThemeMode(_i3.ThemeMode? value) => - super.noSuchMethod(Invocation.setter(#userThemeMode, value), - returnValueForMissingStub: null); - @override - void clearPreferences() => - super.noSuchMethod(Invocation.method(#clearPreferences, []), - returnValueForMissingStub: null); -} - -/// A class which mocks [StatusBarService]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockStatusBarService extends _i1.Mock implements _i4.StatusBarService { - @override - _i5.Future? updateStatusBarColor(_i6.Color? statusBarColor) => - (super.noSuchMethod( - Invocation.method(#updateStatusBarColor, [statusBarColor])) - as _i5.Future?); -} - -/// A class which mocks [PlatformService]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPlatformService extends _i1.Mock implements _i7.PlatformService { - @override - bool get isIos => - (super.noSuchMethod(Invocation.getter(#isIos), returnValue: false) - as bool); - @override - bool get isAndroid => - (super.noSuchMethod(Invocation.getter(#isAndroid), returnValue: false) - as bool); - @override - bool get isMobilePlatform => - (super.noSuchMethod(Invocation.getter(#isMobilePlatform), - returnValue: false) as bool); -} diff --git a/packages/stacked_themes/test/theme_manager_test.dart b/packages/stacked_themes/test/theme_manager_test.dart deleted file mode 100644 index 0e2cbf729..000000000 --- a/packages/stacked_themes/test/theme_manager_test.dart +++ /dev/null @@ -1,414 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; -import 'package:stacked_themes/src/theme_manager.dart'; - -import 'test_setup.dart'; - -void main() { - group('ThemeManagerTest -', () { - setUp(() => registerServices()); - - tearDown(() => unregisterServices()); - - group('Construction -', () { - test( - 'When constructed and last theme index is 1, should broadcast theme at 1 as selected theme', - () async { - getAndRegisterSharedPreferencesServiceMock(themeIndex: 1); - - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - - var themeManager = ThemeManager(themes: themes); - themeManager.themesStream.listen(expectAsync1((theme) { - expect(theme.selectedTheme, themes[1]); - })); - }); - - test( - 'When constructed and last theme index is 2, if the new theme manager has less themes, broadcast first', - () async { - getAndRegisterSharedPreferencesServiceMock(themeIndex: 2); - - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - - var themeManager = ThemeManager(themes: themes); - themeManager.themesStream.listen(expectAsync1((theme) { - expect(theme.selectedTheme, themes.first); - })); - }); - - test( - 'When constructed and last theme index is 2, if the new theme manager has less themes, reset theme', - () async { - var sharedPreferences = - getAndRegisterSharedPreferencesServiceMock(themeIndex: 2); - - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - - ThemeManager(themes: themes); - verify(sharedPreferences.themeIndex = null); - }); - - test( - 'When constructed and no theme has been selected we broadcast the first theme', - () async { - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - - var themeManager = ThemeManager(themes: themes); - themeManager.themesStream.listen(expectAsync1((theme) { - expect(theme.selectedTheme, themes.first); - })); - }); - }); - - group('themesStream -', () { - test('After construction, should broadcast the first theme from themes', - () { - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - var themeManager = ThemeManager(themes: themes); - themeManager.themesStream.listen(expectAsync1((theme) { - expect(theme.selectedTheme, themes.first); - })); - }); - }); - - group('selectThemeAtIndex -', () { - test('When called and no no themes were supplied should throw exception', - () { - var themeManager = ThemeManager( - lightTheme: ThemeData(), - darkTheme: ThemeData(), - ); - - expect(() => themeManager.selectThemeAtIndex(1), throwsException); - }); - - test( - 'When called with index 1, should broadcast the theme at 1 over the themesStream', - () { - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - var themeManager = ThemeManager(themes: themes); - bool alreadyCalled = false; - themeManager.themesStream.listen(expectAsync1((theme) { - if (alreadyCalled) { - expect(theme.selectedTheme, themes[1]); - } - alreadyCalled = true; - }, count: 2)); - - themeManager.selectThemeAtIndex(1); - }); - - test('When called with index, should get status color from the callback', - () async { - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - bool called = false; - var themeManager = ThemeManager( - themes: themes, - statusBarColorBuilder: (themeData) { - called = true; - return null; - }); - - await themeManager.selectThemeAtIndex(1); - - expect(called, true); - }); - - test( - 'When called with index, should pass color to the status bar service', - () async { - var statusBar = getAndRegisterStatusBarServiceMock(); - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - var themeManager = ThemeManager( - themes: themes, - statusBarColorBuilder: (theme) => theme!.primaryColor, - ); - - await themeManager.selectThemeAtIndex(1); - verify(statusBar.updateStatusBarColor(themes.first.primaryColor)); - }); - }); - - group('selectedThemeIndex -', () { - test('When called should and we do not have multiple themes return null', - () { - var themeManager = ThemeManager( - lightTheme: ThemeData(), - darkTheme: ThemeData(), - statusBarColorBuilder: (theme) => theme!.primaryColor, - ); - - expect(themeManager.selectedThemeIndex, null); - }); - - test( - 'When sharedPreferences theme index returns 5, selectedThemeIndex should return 5', - () async { - getAndRegisterSharedPreferencesServiceMock(themeIndex: 5); - var themeManager = ThemeManager( - themes: [ - ThemeData(), - ThemeData(), - ], - statusBarColorBuilder: (theme) => theme!.primaryColor, - ); - - expect(themeManager.selectedThemeIndex, 5); - }); - - test( - 'When themeIndex from sharedPreferences returns null, theme index should default to 0', - () { - var themeManager = ThemeManager( - themes: [ - ThemeData(), - ThemeData(), - ], - statusBarColorBuilder: (theme) => theme!.primaryColor, - ); - - expect(themeManager.selectedThemeIndex, 0); - }); - }); - - group('Dark and Light -', () { - test( - 'When constructed with ThemeMode.system, should broadcast ThemeMode.system in the ThemeModel', - () { - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - var themeManager = - ThemeManager(darkTheme: themes.first, lightTheme: themes.last); - - themeManager.themesStream.listen(expectAsync1((theme) { - expect(theme.themeMode, ThemeMode.system); - })); - }); - - test( - 'When constructed with ThemeMode.light, should broadcast ThemeMode.light in the ThemeModel', - () { - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - var themeManager = ThemeManager( - darkTheme: themes.first, - lightTheme: themes.last, - defaultTheme: ThemeMode.light, - ); - - themeManager.themesStream.listen(expectAsync1((theme) { - expect(theme.themeMode, ThemeMode.light); - })); - }); - - test( - 'When constructed with ThemeMode.system, should check if user has a savedThemeMode', - () { - var sharedPreferences = getAndRegisterSharedPreferencesServiceMock(); - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - ThemeManager( - darkTheme: themes.first, - lightTheme: themes.last, - defaultTheme: ThemeMode.system, - ); - - verify(sharedPreferences.userThemeMode); - }); - - test( - 'When constructed with ThemeMode.light, and user has saved theme dark, should broadcast dark', - () { - getAndRegisterSharedPreferencesServiceMock( - userThemeMode: ThemeMode.dark, - ); - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - var manager = ThemeManager( - darkTheme: themes.first, - lightTheme: themes.last, - defaultTheme: ThemeMode.light, - ); - - manager.themesStream.listen(expectAsync1((theme) { - expect(theme.themeMode, ThemeMode.dark); - })); - }); - - test( - 'When manager constructed with Light, When toggleDarkLightTheme is called, should broadcast Dark theme mode', - () { - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - var manager = ThemeManager( - darkTheme: themes.first, - lightTheme: themes.last, - defaultTheme: ThemeMode.light, - ); - - bool alreadyCalled = false; - manager.themesStream.listen(expectAsync1((theme) { - if (alreadyCalled) { - expect(theme.themeMode, ThemeMode.dark); - } - alreadyCalled = true; - }, count: 2)); - - manager.toggleDarkLightTheme(); - }); - - test( - 'When manager constructed with Light, should get the statusColor from the builder', - () { - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - var statusColor; - ThemeManager( - darkTheme: themes.first, - lightTheme: themes.last, - defaultTheme: ThemeMode.light, - statusBarColorBuilder: (theme) { - statusColor = theme?.primaryColor; - return statusColor; - }); - - expect(statusColor, themes.last.primaryColor); - }); - - test( - 'When manager constructed with Light, When toggleDarkLightTheme is called, should get the statusColor from the builder', - () { - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - var statusColor; - var manager = ThemeManager( - darkTheme: themes.first, - lightTheme: themes.last, - defaultTheme: ThemeMode.light, - statusBarColorBuilder: (theme) { - statusColor = theme?.primaryColor; - return statusColor; - }); - - manager.toggleDarkLightTheme(); - expect(statusColor, themes.first.primaryColor); - }); - }); - - group('setThemeMode -', () { - test( - 'When called with ThemeMode.dark, should save the theme mode to shared preferences', - () { - var preferences = getAndRegisterSharedPreferencesServiceMock(); - var themeManager = ThemeManager( - lightTheme: ThemeData(), - darkTheme: ThemeData(), - ); - themeManager.setThemeMode(ThemeMode.dark); - verify(preferences.userThemeMode = ThemeMode.dark); - }); - - test( - 'When called with ThemeMode.dark, should set the status bar color from the ThemeManager', - () async { - bool called = false; - var themeManager = ThemeManager( - lightTheme: ThemeData(), - darkTheme: ThemeData(), - statusBarColorBuilder: (themeData) { - called = true; - return null; - }); - - themeManager.setThemeMode(ThemeMode.dark); - - expect(called, true); - }); - - test( - 'When called with ThemeMode.dark, should broadcast the theme data over the theme stream', - () { - var themeManager = ThemeManager( - lightTheme: ThemeData(), - darkTheme: ThemeData(), - ); - - bool alreadyCalled = false; - themeManager.themesStream.listen(expectAsync1((theme) { - if (alreadyCalled) { - expect(theme.themeMode, ThemeMode.dark); - } - alreadyCalled = true; - }, count: 2)); - - themeManager.setThemeMode(ThemeMode.dark); - }); - }); - - group('Theme Persistence -', () { - test( - 'When a theme changes the index should be persisted into shared preferences', - () async { - var sharedPreferences = getAndRegisterSharedPreferencesServiceMock(); - var themes = [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]; - var themeManager = ThemeManager(themes: themes); - await themeManager.selectThemeAtIndex(1); - verify(sharedPreferences.themeIndex = 1); - }); - - test( - 'When theme manager contructed, should get the themeIndex from the shared preferences', - () { - var sharedPreferences = getAndRegisterSharedPreferencesServiceMock(); - ThemeManager(themes: [ - ThemeData(primaryColor: Colors.blue), - ThemeData(primaryColor: Colors.yellow), - ]); - verify(sharedPreferences.themeIndex); - }); - }); - }); -} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 000000000..0446b20e1 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,31 @@ +name: stacked +description: The framework to build testable, scalable and maintainable flutter apps +version: 3.4.4 +homepage: https://github.com/FilledStacks/stacked +platforms: + android: + ios: + linux: + macos: + web: + windows: + +environment: + sdk: ">=3.5.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + get_it: ^8.0.2 + meta: ^1.15.0 + provider: ^6.1.2 + collection: ^1.18.0 + # Remove the path and use pub one after publish the stacked_shared + stacked_shared: ^1.4.2 + universal_io: ^2.2.2 + path: ^1.9.0 + +dev_dependencies: + flutter_lints: ^5.0.0 + flutter_test: + sdk: flutter diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..7a0054035 --- /dev/null +++ b/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["local>Stacked-Org/renovate-config"] +} diff --git a/packages/stacked/test/baseviewmodel_test.dart b/test/base_viewmodel_test.dart similarity index 93% rename from packages/stacked/test/baseviewmodel_test.dart rename to test/base_viewmodel_test.dart index 7b44b429f..5d57d2456 100644 --- a/packages/stacked/test/baseviewmodel_test.dart +++ b/test/base_viewmodel_test.dart @@ -22,7 +22,7 @@ class TestViewModel extends BaseViewModel { } Future _futureToRun(bool fail) async { - await Future.delayed(Duration(milliseconds: 50)); + await Future.delayed(const Duration(milliseconds: 50)); if (fail) { throw Exception('Broken Future'); } @@ -57,21 +57,21 @@ void main() { 'when skeletonData is called and Key passed is busy should return busyData', () { var viewModel = TestViewModel(); - const String TEST_KEY = 'test-key'; - viewModel.setBusyForObject(TEST_KEY, true); + const String testKey = 'test-key'; + viewModel.setBusyForObject(testKey, true); var data = viewModel.skeletonData( - realData: 'Real Data', busyData: 'Test', busyKey: TEST_KEY); + realData: 'Real Data', busyData: 'Test', busyKey: testKey); expect(data, 'Test'); }); test( 'when skeletonData is called and Key passed is not busy but model is busy should return realData', () { var viewModel = TestViewModel(); - const String TEST_KEY = 'test-key'; - viewModel.setBusyForObject(TEST_KEY, false); + const String testKey = 'test-key'; + viewModel.setBusyForObject(testKey, false); viewModel.setBusy(true); var data = viewModel.skeletonData( - realData: 'Real Data', busyData: 'Test', busyKey: TEST_KEY); + realData: 'Real Data', busyData: 'Test', busyKey: testKey); expect(data, 'Real Data'); }); test( @@ -93,7 +93,7 @@ void main() { test( 'When setBusyForObject is called with parameter true busy for that object should be true', () { - var property; + String property = '-'; var viewModel = TestViewModel(); viewModel.setBusyForObject(property, true); expect(viewModel.busy(property), true); @@ -102,7 +102,7 @@ void main() { test( 'When setBusyForObject is called with true then false, should be false', () { - var property; + String property = '-'; var viewModel = TestViewModel(); viewModel.setBusyForObject(property, true); viewModel.setBusyForObject(property, false); diff --git a/test/form_state_helper_test.dart b/test/form_state_helper_test.dart new file mode 100644 index 000000000..26e8ed758 --- /dev/null +++ b/test/form_state_helper_test.dart @@ -0,0 +1,89 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:stacked/stacked.dart'; + +class TestFormViewModel extends BaseViewModel with FormStateHelper { + bool setFormStatusCalled = false; + @override + void setFormStatus() { + setFormStatusCalled = true; + } +} + +void main() { + group('FormStateHelperTest -', () { + test('When initialised showValidationMessage Should be false', () { + final viewmodel = TestFormViewModel(); + + expect(viewmodel.showValidationMessage, isFalse); + }); + test('When initialised validationMessage Should be null', () { + final viewmodel = TestFormViewModel(); + + expect(viewmodel.validationMessage, isNull); + }); + test('When initialised formValueMap Should be empty', () { + final viewmodel = TestFormViewModel(); + + expect(viewmodel.formValueMap, isEmpty); + }); + test('When initialised fieldsValidationMessages Should be empty', () { + final viewmodel = TestFormViewModel(); + + expect(viewmodel.fieldsValidationMessages, isEmpty); + }); + + group('setValidationMessage -', () { + test('When set validation message, validationMessage Should not be null', + () { + final viewmodel = TestFormViewModel(); + viewmodel.setValidationMessage('value'); + expect(viewmodel.validationMessage, isNotNull); + }); + test('When set validation message, validationMessage Should not be null', + () { + final viewmodel = TestFormViewModel(); + viewmodel.setValidationMessage('value'); + expect(viewmodel.validationMessage, isNotNull); + }); + }); + group('setData -', () { + test( + 'When set new data for a field, Should reset the validation message of that field if exist', + () { + final viewmodel = TestFormViewModel(); + viewmodel.setValidationMessages({'name': 'Name is not valid!'}); + viewmodel.setData({'name': 'newName'}); + + expect(viewmodel.fieldsValidationMessages['name'], isNull); + }); + test('When set data, Should call the setFormStatus method', () { + final viewmodel = TestFormViewModel(); + viewmodel.setData({'name': 'newName'}); + + expect(viewmodel.setFormStatusCalled, isTrue); + }); + }); + group('setValidationMessages -', () { + test( + 'When called with empty map, Should remove all old validation messages', + () { + final viewmodel = TestFormViewModel(); + viewmodel.setValidationMessages({}); + + expect(viewmodel.fieldsValidationMessages, isEmpty); + }); + test('When called with a value with null, Should filter it out', () { + final viewmodel = TestFormViewModel(); + viewmodel.setValidationMessages({'name': null}); + + expect(viewmodel.fieldsValidationMessages, isEmpty); + }); + test('When called with a non-nullable value, Should add it', () { + final viewmodel = TestFormViewModel(); + viewmodel.setValidationMessages({'name': 'tName'}); + + expect(viewmodel.fieldsValidationMessages['name'], 'tName'); + }); + }); + }); +} diff --git a/packages/stacked/test/futureviewmodel_test.dart b/test/future_viewmodel_test.dart similarity index 66% rename from packages/stacked/test/futureviewmodel_test.dart rename to test/future_viewmodel_test.dart index 95a87291c..61d1784e2 100644 --- a/packages/stacked/test/futureviewmodel_test.dart +++ b/test/future_viewmodel_test.dart @@ -1,7 +1,8 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:stacked/stacked.dart'; +import 'package:stacked/src/view_models/data_models/multiple_data_models.dart'; +import 'package:stacked/src/view_models/data_models/single_data_models.dart'; -const _SingleFutureExceptionFailMessage = 'Future to Run failed'; +const _singleFutureExceptionFailMessage = 'Future to Run failed'; class TestFutureViewModel extends FutureViewModel { final bool fail; @@ -19,41 +20,48 @@ class TestFutureViewModel extends FutureViewModel { @override Future futureToRun() async { - await Future.delayed(Duration(milliseconds: 20)); - if (fail) throw Exception(_SingleFutureExceptionFailMessage); + await Future.delayed(const Duration(milliseconds: 20)); + if (fail) throw Exception(_singleFutureExceptionFailMessage); return numberToReturn; } + void setErrorManually() { + setError('my error'); + } + @override void onData(int? data) { dataCalled = true; } } -const String NumberDelayFuture = 'delayedNumber'; -const String StringDelayFuture = 'delayedString'; -const String _NumberDelayExceptionMessage = 'getNumberAfterDelay failed'; +const String numberDelayFuture = 'delayedNumber'; +const String stringDelayFuture = 'delayedString'; +const String _numberDelayExceptionMessage = 'getNumberAfterDelay failed'; class TestMultipleFutureViewModel extends MultipleFutureViewModel { final bool failOne; final int futureOneDuration; final int futureTwoDuration; - TestMultipleFutureViewModel( - {this.failOne = false, - this.futureOneDuration = 300, - this.futureTwoDuration = 400}); + bool hasCalledOnAllFuturesCompleted; + TestMultipleFutureViewModel({ + this.failOne = false, + this.futureOneDuration = 300, + this.futureTwoDuration = 400, + this.hasCalledOnAllFuturesCompleted = false, + }); int numberToReturn = 5; @override Map get futuresMap => { - NumberDelayFuture: getNumberAfterDelay, - StringDelayFuture: getStringAfterDelay, + numberDelayFuture: getNumberAfterDelay, + stringDelayFuture: getStringAfterDelay, }; Future getNumberAfterDelay() async { if (failOne) { - throw Exception(_NumberDelayExceptionMessage); + throw Exception(_numberDelayExceptionMessage); } await Future.delayed(Duration(milliseconds: futureOneDuration)); return numberToReturn; @@ -63,10 +71,21 @@ class TestMultipleFutureViewModel extends MultipleFutureViewModel { await Future.delayed(Duration(milliseconds: futureTwoDuration)); return 'String data'; } + + @override + void onAllFuturesCompleted() { + hasCalledOnAllFuturesCompleted = true; + } } void main() { group('FutureViewModel', () { + test('When error is manually set, hasError should be true', () async { + var futureViewModel = TestFutureViewModel(); + futureViewModel.setErrorManually(); + + expect(futureViewModel.hasError, isTrue); + }); test('When future is complete data should be set and ready', () async { var futureViewModel = TestFutureViewModel(); await futureViewModel.initialise(); @@ -108,7 +127,7 @@ void main() { var futureViewModel = TestFutureViewModel(fail: true); await futureViewModel.initialise(); expect(futureViewModel.modelError.message, - _SingleFutureExceptionFailMessage); + _singleFutureExceptionFailMessage); }); test('When a future fails onData should not be called', () async { @@ -143,8 +162,25 @@ void main() { var futureViewModel = TestMultipleFutureViewModel(); await futureViewModel.initialise(); - expect(futureViewModel.dataMap![NumberDelayFuture], 5); - expect(futureViewModel.dataMap![StringDelayFuture], 'String data'); + expect(futureViewModel.dataMap![numberDelayFuture], 5); + expect(futureViewModel.dataMap![stringDelayFuture], 'String data'); + }); + + test('When running multiple futures onAllFuturesCompleted should be called', + () async { + var futureViewModel = TestMultipleFutureViewModel(); + await futureViewModel.initialise(); + + expect(futureViewModel.hasCalledOnAllFuturesCompleted, true); + }); + + test( + 'When one of multiple futures fail onAllFuturesCompleted should still be called', + () async { + var futureViewModel = TestMultipleFutureViewModel(failOne: true); + await futureViewModel.initialise(); + + expect(futureViewModel.hasCalledOnAllFuturesCompleted, true); }); test( @@ -153,8 +189,8 @@ void main() { var futureViewModel = TestMultipleFutureViewModel(failOne: true); await futureViewModel.initialise(); - expect(futureViewModel.hasErrorForKey(NumberDelayFuture), true); - expect(futureViewModel.hasErrorForKey(StringDelayFuture), false); + expect(futureViewModel.hasErrorForKey(numberDelayFuture), true); + expect(futureViewModel.hasErrorForKey(stringDelayFuture), false); }); test( @@ -163,8 +199,8 @@ void main() { var futureViewModel = TestMultipleFutureViewModel(failOne: true); await futureViewModel.initialise(); - expect(futureViewModel.dataMap![NumberDelayFuture], null); - expect(futureViewModel.dataMap![StringDelayFuture], 'String data'); + expect(futureViewModel.dataMap![numberDelayFuture], null); + expect(futureViewModel.dataMap![stringDelayFuture], 'String data'); }); test('When multiple futures run the key should be set to indicate busy', @@ -172,8 +208,8 @@ void main() { var futureViewModel = TestMultipleFutureViewModel(); futureViewModel.initialise(); - expect(futureViewModel.busy(NumberDelayFuture), true); - expect(futureViewModel.busy(StringDelayFuture), true); + expect(futureViewModel.busy(numberDelayFuture), true); + expect(futureViewModel.busy(stringDelayFuture), true); }); test( @@ -182,26 +218,26 @@ void main() { var futureViewModel = TestMultipleFutureViewModel(); await futureViewModel.initialise(); - expect(futureViewModel.busy(NumberDelayFuture), false); - expect(futureViewModel.busy(StringDelayFuture), false); + expect(futureViewModel.busy(numberDelayFuture), false); + expect(futureViewModel.busy(stringDelayFuture), false); }); test('When a future fails busy should be set to false', () async { var futureViewModel = TestMultipleFutureViewModel(failOne: true); await futureViewModel.initialise(); - expect(futureViewModel.busy(NumberDelayFuture), false); - expect(futureViewModel.busy(StringDelayFuture), false); + expect(futureViewModel.busy(numberDelayFuture), false); + expect(futureViewModel.busy(stringDelayFuture), false); }); test('When a future fails should set error for future key', () async { var futureViewModel = TestMultipleFutureViewModel(failOne: true); await futureViewModel.initialise(); - expect(futureViewModel.error(NumberDelayFuture).message, - _NumberDelayExceptionMessage); + expect(futureViewModel.error(numberDelayFuture).message, + _numberDelayExceptionMessage); - expect(futureViewModel.error(StringDelayFuture), null); + expect(futureViewModel.error(stringDelayFuture), null); }); test( @@ -210,9 +246,9 @@ void main() { var futureViewModel = TestMultipleFutureViewModel( futureOneDuration: 10, futureTwoDuration: 60); futureViewModel.initialise(); - await Future.delayed(Duration(milliseconds: 30)); + await Future.delayed(const Duration(milliseconds: 30)); - expect(futureViewModel.busy(NumberDelayFuture), false, + expect(futureViewModel.busy(numberDelayFuture), false, reason: 'String future should be done at this point'); expect(futureViewModel.anyObjectsBusy, true, reason: 'Should be true because second future is still running'); @@ -222,11 +258,11 @@ void main() { test('notifySourceChanged - When called should re-run Future', () async { var futureViewModel = TestMultipleFutureViewModel(); await futureViewModel.initialise(); - expect(futureViewModel.dataMap![NumberDelayFuture], 5); + expect(futureViewModel.dataMap![numberDelayFuture], 5); futureViewModel.numberToReturn = 10; futureViewModel.notifySourceChanged(); await futureViewModel.initialise(); - expect(futureViewModel.dataMap![NumberDelayFuture], 10); + expect(futureViewModel.dataMap![numberDelayFuture], 10); }); }); }); diff --git a/test/index_tracking_state_helper_test.dart b/test/index_tracking_state_helper_test.dart new file mode 100644 index 000000000..6985ba7c0 --- /dev/null +++ b/test/index_tracking_state_helper_test.dart @@ -0,0 +1,430 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stacked/src/router/matcher/route_matcher.dart'; +import 'package:stacked/stacked.dart'; + +class TestIndexTrackingViewModel extends BaseViewModel + with IndexTrackingStateHelper {} + +class TestRouterService implements RouterServiceInterface { + final RootStackRouter testRouter; + final RouteData testTopRoute; + const TestRouterService({ + required this.testRouter, + required this.testTopRoute, + }); + + @override + RootStackRouter get router => testRouter; + + @override + RouteData get topRoute => testTopRoute; +} + +class TestRouter extends RootStackRouter { + final Map testPagesMap; + final List testRoutes; + TestRouter({this.testPagesMap = const {}, this.testRoutes = const []}); + + @override + Map get pagesMap => testPagesMap; + + @override + List get routes => testRoutes; +} + +class TestRoutingController extends RoutingController { + @override + bool canPop({ + bool ignoreChildRoutes = false, + bool ignoreParentRoutes = false, + bool ignorePagelessRoutes = false, + }) { + // TODO: implement canPop + throw UnimplementedError(); + } + + @override + // TODO: implement canPopSelfOrChildren + bool get canPopSelfOrChildren => throw UnimplementedError(); + + @override + // TODO: implement current + RouteData get current => throw UnimplementedError(); + + @override + // TODO: implement currentChild + RouteData? get currentChild => throw UnimplementedError(); + + @override + // TODO: implement key + LocalKey get key => throw UnimplementedError(); + + @override + // TODO: implement managedByWidget + bool get managedByWidget => throw UnimplementedError(); + + @override + // TODO: implement matcher + RouteMatcher get matcher => throw UnimplementedError(); + + @override + // TODO: implement pageBuilder + PageBuilder get pageBuilder => throw UnimplementedError(); + + @override + Future pop([T? result]) { + // TODO: implement pop + throw UnimplementedError(); + } + + @override + // TODO: implement routeCollection + RouteCollection get routeCollection => throw UnimplementedError(); + + @override + // TODO: implement routeData + RouteData get routeData => throw UnimplementedError(); + + @override + // TODO: implement stack + List get stack => throw UnimplementedError(); + + @override + RoutingController topMostRouter({bool ignorePagelessRoutes = false}) { + // TODO: implement topMostRouter + throw UnimplementedError(); + } + + @override + void updateRouteData(RouteData data) { + // TODO: implement updateRouteData + } +} + +final bottomNavExampleRouteMatch = RouteMatch( + key: UniqueKey(), + name: 'BottomNavExample', + path: 'bottom-nav-example', + segments: const [], + stringMatch: '', +); + +final favoriteRouteMatch = RouteMatch( + key: UniqueKey(), + name: 'FavoriteView', + path: 'favorite-view', + segments: const [], + stringMatch: '', +); + +final historyRouteMatch = RouteMatch( + key: UniqueKey(), + name: 'HistoryView', + path: 'history-view', + segments: const [], + stringMatch: '', +); + +final profileRouteMatch = RouteMatch( + key: UniqueKey(), + name: 'ProfileView', + path: 'profile-view', + segments: const [], + stringMatch: '', +); + +RouteData getBottomNavExampleRouteData({ + List pendingChildren = const [], +}) => + RouteData( + route: bottomNavExampleRouteMatch, + router: TestRoutingController(), + pendingChildren: pendingChildren, + ); + +RouteConfig getRouteConfig(String name, {List? children}) => + RouteConfig( + name, + path: toSnakeCase(name), + children: children, + ); + +String toSnakeCase(String text) { + return text.replaceAll(RegExp(r'(? _counter; - List _counters = []; + final List _counters = []; List get counters => _counters; void updateCounter() { @@ -19,8 +19,8 @@ class CounterService with ReactiveServiceMixin { } } -class ListCounterService with ReactiveServiceMixin { - List _counters = []; +class ListCounterService with ListenableServiceMixin { + final List _counters = []; List get counters => _counters; int _counter = 0; @@ -30,8 +30,8 @@ class ListCounterService with ReactiveServiceMixin { } } -class SetCounterService with ReactiveServiceMixin { - List _counters = []; +class SetCounterService with ListenableServiceMixin { + final List _counters = []; List get counters => _counters; int _counter = 0; @@ -56,7 +56,7 @@ void main() { // Have to wait for the listener to be called above. In real life the results is not // expected to happen in the same CPU cycle so this is perfect for a unit test. - await Future.delayed(Duration(milliseconds: 10)); + await Future.delayed(const Duration(milliseconds: 10)); expect(called, true); }); @@ -74,7 +74,7 @@ void main() { // Have to wait for the listener to be called above. In real life the results is not // expected to happen in the same CPU cycle so this is perfect for a unit test. - await Future.delayed(Duration(milliseconds: 10)); + await Future.delayed(const Duration(milliseconds: 10)); expect(called, true); }); @@ -92,7 +92,7 @@ void main() { // Have to wait for the listener to be called above. In real life the results is not // expected to happen in the same CPU cycle so this is perfect for a unit test. - await Future.delayed(Duration(milliseconds: 10)); + await Future.delayed(const Duration(milliseconds: 10)); expect(called, true); }); @@ -110,7 +110,7 @@ void main() { // Have to wait for the listener to be called above. In real life the results is not // expected to happen in the same CPU cycle so this is perfect for a unit test. - await Future.delayed(Duration(milliseconds: 10)); + await Future.delayed(const Duration(milliseconds: 10)); expect(called, true); }); diff --git a/packages/stacked/test/reactive_viewmodel_test.dart b/test/reactive_viewmodel_test.dart similarity index 72% rename from packages/stacked/test/reactive_viewmodel_test.dart rename to test/reactive_viewmodel_test.dart index 03ba6d6e2..ebcbe23d2 100644 --- a/packages/stacked/test/reactive_viewmodel_test.dart +++ b/test/reactive_viewmodel_test.dart @@ -1,8 +1,9 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:stacked/src/state_management/base_view_models.dart'; -import 'package:stacked/src/state_management/reactive_service_mixin.dart'; +import 'package:stacked/src/mixins/listenable_service_mixin.dart'; +import 'package:stacked/src/view_models/base_view_models.dart'; +import 'package:stacked/src/view_models/data_models/single_data_models.dart'; -class TestReactiveService with ReactiveServiceMixin { +class TestReactiveService with ListenableServiceMixin { int _counter = 0; int get counter => _counter; @@ -15,7 +16,7 @@ class TestReactiveService with ReactiveServiceMixin { class TestReactiveViewModel extends ReactiveViewModel { final _testService = TestReactiveService(); @override - List get reactiveServices => [_testService]; + List get listenableServices => [_testService]; void updateCounter() { _testService.updateCounter(); @@ -25,11 +26,11 @@ class TestReactiveViewModel extends ReactiveViewModel { class TestFutureReactiveViewModel extends FutureViewModel { final _testService = TestReactiveService(); @override - List get reactiveServices => [_testService]; + List get listenableServices => [_testService]; @override Future futureToRun() async { - await Future.delayed(Duration(milliseconds: 5)); + await Future.delayed(const Duration(milliseconds: 5)); return 1; } @@ -49,7 +50,7 @@ void main() { called = true; }); viewModel.updateCounter(); - await Future.delayed(Duration(milliseconds: 5)); + await Future.delayed(const Duration(milliseconds: 5)); expect(called, true); }); test( @@ -61,7 +62,7 @@ void main() { called = true; }); viewModel.updateCounter(); - await Future.delayed(Duration(milliseconds: 5)); + await Future.delayed(const Duration(milliseconds: 5)); expect(called, true); }); test('Given a reactive service should not notifyListeners after disposed', @@ -73,7 +74,7 @@ void main() { }); viewModel.dispose(); viewModel.updateCounter(); - await Future.delayed(Duration(milliseconds: 5)); + await Future.delayed(const Duration(milliseconds: 5)); expect(called, false); }); }); diff --git a/packages/stacked/test/stacked_test.dart b/test/stacked_test.dart similarity index 73% rename from packages/stacked/test/stacked_test.dart rename to test/stacked_test.dart index 212e21ac1..0da434d92 100644 --- a/packages/stacked/test/stacked_test.dart +++ b/test/stacked_test.dart @@ -1,7 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:stacked/stacked.dart'; - void main() { test('adds one to input values', () {}); } diff --git a/packages/stacked/test/streamviewmodel_test.dart b/test/stream_viewmodel_test.dart similarity index 77% rename from packages/stacked/test/streamviewmodel_test.dart rename to test/stream_viewmodel_test.dart index f6ea489ef..04433cc2f 100644 --- a/packages/stacked/test/streamviewmodel_test.dart +++ b/test/stream_viewmodel_test.dart @@ -1,13 +1,15 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:stacked/stacked.dart'; -Stream numberStream(int dataBack, {required bool fail, int? delay}) async* { +Stream numberStream(int dataBack, + {required bool fail, int? delay}) async* { if (fail) throw Exception('numberStream failed'); if (delay != null) await Future.delayed(Duration(milliseconds: delay)); yield dataBack; } -Stream textStream(String dataBack, {required bool fail, int? delay}) async* { +Stream textStream(String dataBack, + {required bool fail, int? delay}) async* { if (fail) throw Exception('textStream failed'); if (delay != null) await Future.delayed(Duration(milliseconds: delay)); yield dataBack; @@ -18,18 +20,21 @@ class TestStreamViewModel extends StreamViewModel { final int delay; TestStreamViewModel({this.fail = false, this.delay = 0}); int? loadedData; + void setErrorManually() { + setError('my error'); + } @override get stream => numberStream(1, fail: fail, delay: delay); @override void onData(int? data) { - loadedData = data; + if (data != null) loadedData = data; } } -const String _NumberStream = 'numberStream'; -const String _StringStream = 'stringStream'; +const String _numberStream = 'numberStream'; +const String _stringStream = 'stringStream'; class TestMultipleStreamViewModel extends MultipleStreamViewModel { final bool failOne; @@ -42,12 +47,12 @@ class TestMultipleStreamViewModel extends MultipleStreamViewModel { Map get streamsMap { getStreamsMapCalls++; return { - _NumberStream: StreamData(numberStream( + _numberStream: StreamData(numberStream( 5, fail: failOne, delay: delay, )), - _StringStream: StreamData(textStream( + _stringStream: StreamData(textStream( "five", fail: false, delay: delay, @@ -66,7 +71,7 @@ class TestMultipleStreamViewModelWithOverrides extends MultipleStreamViewModel { int? loadedData; @override Map get streamsMap => { - _NumberStream: StreamData( + _numberStream: StreamData( numberStream(5, fail: false, delay: 0), onData: _loadData, ) @@ -82,7 +87,7 @@ void main() async { test('When stream data is fetched data should be set and ready', () async { var streamViewModel = TestStreamViewModel(); streamViewModel.initialise(); - await Future.delayed(Duration(milliseconds: 1)); + await Future.delayed(const Duration(milliseconds: 5)); expect(streamViewModel.data, 1); expect(streamViewModel.dataReady, true); }); @@ -90,15 +95,21 @@ void main() async { () async { var streamViewModel = TestStreamViewModel(); streamViewModel.initialise(); - await Future.delayed(Duration(milliseconds: 1)); + await Future.delayed(const Duration(milliseconds: 1)); expect(streamViewModel.loadedData, 1); }); - + test('When error is manually set, hasError should be true', () async { + var streamViewModel = TestStreamViewModel(); + streamViewModel.initialise(); + await Future.delayed(const Duration(milliseconds: 1)); + streamViewModel.setErrorManually(); + expect(streamViewModel.hasError, isTrue); + }); test('When a stream fails it should indicate there\'s an error and no data', () async { var streamViewModel = TestStreamViewModel(fail: true); streamViewModel.initialise(); - await Future.delayed(Duration(milliseconds: 1)); + await Future.delayed(const Duration(milliseconds: 1)); expect(streamViewModel.hasError, true); expect(streamViewModel.data, null, reason: 'No data should be set when there\'s a failure.'); @@ -108,7 +119,7 @@ void main() async { test('Before a stream returns it should indicate not ready', () async { var streamViewModel = TestStreamViewModel(delay: 1000); streamViewModel.initialise(); - await Future.delayed(Duration(milliseconds: 1)); + await Future.delayed(const Duration(milliseconds: 1)); expect(streamViewModel.dataReady, false); }); @@ -119,7 +130,7 @@ void main() async { listenersCalled = true; }); streamViewModel.initialise(); - await Future.delayed(Duration(milliseconds: 100)); + await Future.delayed(const Duration(milliseconds: 100)); expect(listenersCalled, true); }); @@ -140,8 +151,8 @@ void main() async { var streamViewModel = TestStreamViewModel(delay: 10); streamViewModel.initialise(); - await Future.delayed(const Duration(milliseconds: 30)); - streamViewModel.notifySourceChanged(); + await Future.delayed(const Duration(milliseconds: 30)); + streamViewModel.notifySourceChanged(); expect(streamViewModel.data, 1); }, @@ -167,9 +178,9 @@ void main() async { () async { var streamViewModel = TestMultipleStreamViewModel(); streamViewModel.initialise(); - await Future.delayed(Duration(milliseconds: 4)); - expect(streamViewModel.dataMap![_NumberStream], 5); - expect(streamViewModel.dataMap![_StringStream], 'five'); + await Future.delayed(const Duration(milliseconds: 4)); + expect(streamViewModel.dataMap![_numberStream], 5); + expect(streamViewModel.dataMap![_stringStream], 'five'); }); test( @@ -177,8 +188,8 @@ void main() async { () async { var streamViewModel = TestMultipleStreamViewModel(failOne: true); streamViewModel.initialise(); - await Future.delayed(Duration(milliseconds: 1)); - expect(streamViewModel.hasErrorForKey(_NumberStream), true); + await Future.delayed(const Duration(milliseconds: 1)); + expect(streamViewModel.hasErrorForKey(_numberStream), true); // Make sure we only have 1 error // expect(streamViewModel.errorMap.values.where((v) => v == true).length, 1); }); @@ -188,17 +199,17 @@ void main() async { () async { var streamViewModel = TestMultipleStreamViewModel(failOne: true); streamViewModel.initialise(); - await Future.delayed(Duration(milliseconds: 1)); - expect(streamViewModel.dataReady(_NumberStream), false); + await Future.delayed(const Duration(milliseconds: 1)); + expect(streamViewModel.dataReady(_numberStream), false); // Delay the first lifecycle can complete - await Future.delayed(Duration(milliseconds: 1)); - expect(streamViewModel.dataReady(_StringStream), true); + await Future.delayed(const Duration(milliseconds: 1)); + expect(streamViewModel.dataReady(_stringStream), true); }); test('When one onData is augmented the data will change', () async { var streamViewModel = TestMultipleStreamViewModelWithOverrides(); streamViewModel.initialise(); - await Future.delayed(Duration(milliseconds: 1)); + await Future.delayed(const Duration(milliseconds: 1)); expect(streamViewModel.loadedData, 5); }); @@ -209,7 +220,7 @@ void main() async { listenersCalled = true; }); streamViewModel.initialise(); - await Future.delayed(Duration(milliseconds: 100)); + await Future.delayed(const Duration(milliseconds: 100)); expect(listenersCalled, true); }); @@ -218,7 +229,7 @@ void main() async { () async { var streamViewModel = TestMultipleStreamViewModel(delay: 50); streamViewModel.initialise(); - await Future.delayed(Duration(milliseconds: 300)); + await Future.delayed(const Duration(milliseconds: 300)); expect(streamViewModel.getStreamsMapCalls, 1); }); @@ -229,7 +240,7 @@ void main() async { streamViewModel.initialise(); expect( - streamViewModel.getSubscriptionForKey(_NumberStream) != null, true); + streamViewModel.getSubscriptionForKey(_numberStream) != null, true); }); test('When disposed, should call onCancel for both streams', () async { @@ -261,7 +272,7 @@ void main() async { await Future.delayed(const Duration(milliseconds: 20)); streamViewModel.notifySourceChanged(); - expect(streamViewModel.dataMap![_NumberStream], 5); + expect(streamViewModel.dataMap![_numberStream], 5); }); test( @@ -273,7 +284,7 @@ void main() async { await Future.delayed(const Duration(milliseconds: 20)); streamViewModel.notifySourceChanged(clearOldData: true); - expect(streamViewModel.dataMap![_NumberStream], null); + expect(streamViewModel.dataMap![_numberStream], null); }); }); } diff --git a/packages/stacked/test/viewmodelbuilder_test.dart b/test/viewmodel_builder_test.dart similarity index 90% rename from packages/stacked/test/viewmodelbuilder_test.dart rename to test/viewmodel_builder_test.dart index d432b2eca..1e39eff9f 100644 --- a/packages/stacked/test/viewmodelbuilder_test.dart +++ b/test/viewmodel_builder_test.dart @@ -7,7 +7,8 @@ import 'package:stacked/stacked.dart'; class TestViewModel extends BaseViewModel {} Widget buildTestableWidget(Widget widget) { - return MediaQuery(data: MediaQueryData(), child: MaterialApp(home: widget)); + return MediaQuery( + data: const MediaQueryData(), child: MaterialApp(home: widget)); } void main() { @@ -22,7 +23,7 @@ void main() { buildTestableWidget(ViewModelBuilder.nonReactive( builder: (context, model, child) { buildCounter++; - return Scaffold(); + return const Scaffold(); }, viewModelBuilder: () => testViewModel)); @@ -52,7 +53,7 @@ void main() { buildTestableWidget(ViewModelBuilder.reactive( builder: (context, model, child) { buildCounter++; - return Scaffold(); + return const Scaffold(); }, viewModelBuilder: () => testViewModel)); await tester.pumpWidget(widget); @@ -84,12 +85,15 @@ void main() { ); await tester.pumpWidget(widget); + // ignore: invalid_use_of_protected_member stateKey.currentState!.dispose(); await tester.pumpWidget(widget); + // ignore: invalid_use_of_protected_member stateKey.currentState!.reassemble(); await tester.pumpWidget(widget); + // ignore: invalid_use_of_protected_member stateKey.currentState!.initState(); await tester.pumpWidget(widget);