From 98594f1aa647dec275075ff5b76dbdd5eef4383e Mon Sep 17 00:00:00 2001 From: Andrea Bizzotto Date: Sat, 7 Jan 2023 16:47:23 +0000 Subject: [PATCH] Update packages (#94) * Start migrating project to new packages * Update Android, iOS, Firebase project * Fix argument type * Refactor folders * Fix import * Update folders * Add pubspec.lock and Podfile.lock to git * Customise Podfile with minimum_deployment_target for all pod libraries https://stackoverflow.com/questions/61823044/flutter-the-ios-simulator-deployment-target-iphoneos-deployment-target-is-se * Move FirestoreService to main app * Remove custom packages * Restore unawaited calls * Start cleaning up buttons code * Create "authentication" feature * Rename onboarding files * Cleanup folders and repositories * Pass UserID to all DB methods that require it * Start implementing GoRouter * Comment out broken code * Move more code to GoRouter * Fix navigation * Add onboarding flow to GoRouter * Cleanup SignInScreen * Cleanup AccountScreen * Remove unused file * Refactor JobsScreen * Start implementing EditJobScreenController * Refactor jobs and entries pages * Update all packages * Fix JobEntriesScreen to only watch the jobStreamProvider * Cleanup JobEntriesScreen, add AsyncValueWidget * Cleanup buttons * Rename some folders * Fixed redirect code * Minor fixes, revert to GoRouter 5.1.1 * Fix to prevent too many redirects on app startup * Delete old tests * Update .gitignore and commit launch.json * Make app runnable on Android (gradle fixes) * Updated linter rules and fixed analysis warnings * Removed old flutter driver tests * More linter fixes * Removed Freezed and build_runner * Removed logger package * Cleanup README * Updated screenshots --- .github/FUNDING.yml | 1 - .../images}/time-tracker-screenshots.png | Bin .gitignore | 10 +- .metadata | 24 +- .vscode/launch.json | 25 + README.md | 390 +--------- analysis_options.yaml | 103 +-- android/.gitignore | 6 + android/app/build.gradle | 33 +- android/app/src/debug/AndroidManifest.xml | 3 +- android/app/src/main/AndroidManifest.xml | 18 +- .../MainActivity.kt | 6 - .../res/drawable-v21/launch_background.xml | 12 + .../app/src/main/res/values-night/styles.xml | 18 + android/app/src/main/res/values/styles.xml | 14 +- android/app/src/profile/AndroidManifest.xml | 3 +- android/build.gradle | 12 +- android/gradle.properties | 1 - .../gradle/wrapper/gradle-wrapper.properties | 3 +- android/settings.gradle | 18 +- ios/.gitignore | 2 + ios/Flutter/AppFrameworkInfo.plist | 4 +- ios/Flutter/Debug.xcconfig | 2 +- ios/Flutter/Release.xcconfig | 2 +- ios/Podfile | 24 +- ios/Podfile.lock | 124 ++++ ios/Runner.xcodeproj/project.pbxproj | 184 ++--- .../xcshareddata/xcschemes/Runner.xcscheme | 10 +- ios/Runner/Info.plist | 6 + ios/Runner/Runner-Bridging-Header.h | 2 +- lib/app/auth_widget.dart | 41 -- lib/app/home/account/account_page.dart | 88 --- lib/app/home/cupertino_home_scaffold.dart | 54 -- lib/app/home/entries/entries_list_tile.dart | 48 -- lib/app/home/entries/entries_page.dart | 33 - lib/app/home/entries/entries_view_model.dart | 71 -- lib/app/home/entries/entry_job.dart | 9 - lib/app/home/home_page.dart | 53 -- .../home/job_entries/job_entries_page.dart | 123 ---- lib/app/home/jobs/job_list_tile.dart | 18 - lib/app/home/jobs/jobs_page.dart | 67 -- lib/app/home/tab_item.dart | 32 - lib/app/onboarding/onboarding_page.dart | 49 -- lib/app/onboarding/onboarding_view_model.dart | 22 - lib/app/sign_in/sign_in_button.dart | 20 - lib/app/sign_in/sign_in_page.dart | 127 ---- lib/app/sign_in/sign_in_view_model.dart | 30 - lib/app/top_level_providers.dart | 26 - lib/generated_plugin_registrant.dart | 22 - lib/main.dart | 77 +- lib/routing/app_router.dart | 47 -- lib/routing/cupertino_tab_view_router.dart | 22 - lib/services/firestore_database.dart | 60 -- lib/services/firestore_path.dart | 7 - lib/src/app.dart | 25 + .../common_widgets/action_text_button.dart | 23 + .../common_widgets/async_value_widget.dart | 40 + lib/{ => src}/common_widgets/avatar.dart | 2 +- .../common_widgets/custom_text_button.dart | 26 + .../common_widgets/date_time_picker.dart | 4 +- .../common_widgets}/empty_content.dart | 0 .../common_widgets/error_message_widget.dart | 13 + .../common_widgets/input_dropdown.dart | 0 .../common_widgets}/list_items_builder.dart | 29 +- lib/src/common_widgets/primary_button.dart | 35 + lib/src/common_widgets/responsive_center.dart | 59 ++ .../responsive_scrollable_card.dart | 28 + .../common_widgets/segmented_control.dart | 1 + lib/src/constants/app_sizes.dart | 36 + lib/src/constants/breakpoints.dart | 5 + lib/{ => src}/constants/keys.dart | 0 lib/{ => src}/constants/strings.dart | 3 +- .../data/firebase_auth_repository.dart | 38 + .../authentication/domain/app_user.dart | 26 + .../authentication/domain/fake_app_user.dart | 11 + .../presentation/account/account_screen.dart | 72 ++ .../account/account_screen_controller.dart | 21 + .../email_password_sign_in_controller.dart | 36 + .../email_password_sign_in_form_type.dart | 55 ++ .../email_password_sign_in_screen.dart | 190 +++++ .../email_password_sign_in_validators.dart | 41 ++ .../email_password/string_validators.dart | 72 ++ .../presentation/sign_in/sign_in_screen.dart | 83 +++ .../sign_in/sign_in_screen_controller.dart | 21 + .../entries/application/entries_service.dart | 92 +++ .../entries/domain}/daily_jobs_details.dart | 2 +- .../domain/entries_list_tile_model.dart | 12 + .../features/entries/domain/entry_job.dart | 9 + .../entries/presentation/entries_screen.dart | 63 ++ .../jobs/data/firestore_data_source.dart | 93 +++ .../jobs/data/firestore_repository.dart | 114 +++ .../features/jobs/domain}/entry.dart | 4 +- .../features/jobs/domain}/job.dart | 6 +- .../edit_job_screen/edit_job_screen.dart} | 74 +- .../edit_job_screen_controller.dart | 48 ++ .../edit_job_screen/job_submit_exception.dart | 9 + .../entry_screen/entry_screen.dart} | 68 +- .../entry_screen/entry_screen_controller.dart | 29 + .../job_entries_screen}/entry_list_item.dart | 8 +- .../job_entries_screen/job_entries_list.dart | 43 ++ .../job_entries_list_controller.dart | 28 + .../job_entries_screen.dart | 55 ++ .../presentation/jobs_screen/jobs_screen.dart | 74 ++ .../jobs_screen/jobs_screen_controller.dart | 28 + .../data/onboarding_repository.dart} | 10 +- .../presentation/onboarding_controller.dart | 21 + .../presentation/onboarding_screen.dart | 52 ++ lib/src/localization/string_hardcoded.dart | 5 + lib/src/routing/app_router.dart | 208 ++++++ lib/src/routing/go_router_refresh_stream.dart | 21 + lib/src/routing/not_found_screen.dart | 18 + .../routing/scaffold_with_bottom_nav_bar.dart | 67 ++ lib/src/utils/alert_dialogs.dart | 12 + lib/src/utils/async_value_ui.dart | 18 + .../job_entries => src/utils}/format.dart | 0 lib/src/utils/show_alert_dialog.dart | 48 ++ .../utils/show_exception_alert_dialog.dart | 42 ++ media/application-layers.png | Bin 133722 -> 0 bytes media/time-tracker-widget-tree.png | Bin 197528 -> 0 bytes pubspec.lock | 684 ++++++++++++++++++ pubspec.yaml | 98 +-- test/mocks.dart | 16 - test/onboarding_view_model_test.dart | 26 - test/shared_preferences_service_test.dart | 41 -- test/sign_in_page_test.dart | 62 -- test/sign_in_view_model_test.dart | 50 -- .../account_screen_controller_test.dart | 120 +++ .../features/jobs/domain}/job_test.dart | 2 +- test/src/mocks.dart | 8 + test_driver/app.dart | 34 - test_driver/app_test.dart | 71 -- test_driver/fake_auth_service.dart | 287 -------- 132 files changed, 3571 insertions(+), 2510 deletions(-) delete mode 100644 .github/FUNDING.yml rename {media => .github/images}/time-tracker-screenshots.png (100%) create mode 100644 .vscode/launch.json create mode 100644 android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 android/app/src/main/res/values-night/styles.xml create mode 100644 ios/Podfile.lock delete mode 100644 lib/app/auth_widget.dart delete mode 100644 lib/app/home/account/account_page.dart delete mode 100644 lib/app/home/cupertino_home_scaffold.dart delete mode 100644 lib/app/home/entries/entries_list_tile.dart delete mode 100644 lib/app/home/entries/entries_page.dart delete mode 100644 lib/app/home/entries/entries_view_model.dart delete mode 100644 lib/app/home/entries/entry_job.dart delete mode 100644 lib/app/home/home_page.dart delete mode 100644 lib/app/home/job_entries/job_entries_page.dart delete mode 100644 lib/app/home/jobs/job_list_tile.dart delete mode 100644 lib/app/home/jobs/jobs_page.dart delete mode 100644 lib/app/home/tab_item.dart delete mode 100644 lib/app/onboarding/onboarding_page.dart delete mode 100644 lib/app/onboarding/onboarding_view_model.dart delete mode 100644 lib/app/sign_in/sign_in_button.dart delete mode 100644 lib/app/sign_in/sign_in_page.dart delete mode 100644 lib/app/sign_in/sign_in_view_model.dart delete mode 100644 lib/app/top_level_providers.dart delete mode 100644 lib/generated_plugin_registrant.dart delete mode 100644 lib/routing/app_router.dart delete mode 100644 lib/routing/cupertino_tab_view_router.dart delete mode 100644 lib/services/firestore_database.dart delete mode 100644 lib/services/firestore_path.dart create mode 100644 lib/src/app.dart create mode 100644 lib/src/common_widgets/action_text_button.dart create mode 100644 lib/src/common_widgets/async_value_widget.dart rename lib/{ => src}/common_widgets/avatar.dart (95%) create mode 100644 lib/src/common_widgets/custom_text_button.dart rename lib/{ => src}/common_widgets/date_time_picker.dart (91%) rename lib/{app/home/jobs => src/common_widgets}/empty_content.dart (100%) create mode 100644 lib/src/common_widgets/error_message_widget.dart rename lib/{ => src}/common_widgets/input_dropdown.dart (100%) rename lib/{app/home/jobs => src/common_widgets}/list_items_builder.dart (54%) create mode 100644 lib/src/common_widgets/primary_button.dart create mode 100644 lib/src/common_widgets/responsive_center.dart create mode 100644 lib/src/common_widgets/responsive_scrollable_card.dart rename lib/{ => src}/common_widgets/segmented_control.dart (98%) create mode 100644 lib/src/constants/app_sizes.dart create mode 100644 lib/src/constants/breakpoints.dart rename lib/{ => src}/constants/keys.dart (100%) rename lib/{ => src}/constants/strings.dart (90%) create mode 100644 lib/src/features/authentication/data/firebase_auth_repository.dart create mode 100644 lib/src/features/authentication/domain/app_user.dart create mode 100644 lib/src/features/authentication/domain/fake_app_user.dart create mode 100644 lib/src/features/authentication/presentation/account/account_screen.dart create mode 100644 lib/src/features/authentication/presentation/account/account_screen_controller.dart create mode 100644 lib/src/features/authentication/presentation/email_password/email_password_sign_in_controller.dart create mode 100644 lib/src/features/authentication/presentation/email_password/email_password_sign_in_form_type.dart create mode 100644 lib/src/features/authentication/presentation/email_password/email_password_sign_in_screen.dart create mode 100644 lib/src/features/authentication/presentation/email_password/email_password_sign_in_validators.dart create mode 100644 lib/src/features/authentication/presentation/email_password/string_validators.dart create mode 100644 lib/src/features/authentication/presentation/sign_in/sign_in_screen.dart create mode 100644 lib/src/features/authentication/presentation/sign_in/sign_in_screen_controller.dart create mode 100644 lib/src/features/entries/application/entries_service.dart rename lib/{app/home/entries => src/features/entries/domain}/daily_jobs_details.dart (96%) create mode 100644 lib/src/features/entries/domain/entries_list_tile_model.dart create mode 100644 lib/src/features/entries/domain/entry_job.dart create mode 100644 lib/src/features/entries/presentation/entries_screen.dart create mode 100644 lib/src/features/jobs/data/firestore_data_source.dart create mode 100644 lib/src/features/jobs/data/firestore_repository.dart rename lib/{app/home/models => src/features/jobs/domain}/entry.dart (96%) rename lib/{app/home/models => src/features/jobs/domain}/job.dart (91%) rename lib/{app/home/jobs/edit_job_page.dart => src/features/jobs/presentation/edit_job_screen/edit_job_screen.dart} (53%) create mode 100644 lib/src/features/jobs/presentation/edit_job_screen/edit_job_screen_controller.dart create mode 100644 lib/src/features/jobs/presentation/edit_job_screen/job_submit_exception.dart rename lib/{app/home/job_entries/entry_page.dart => src/features/jobs/presentation/entry_screen/entry_screen.dart} (68%) create mode 100644 lib/src/features/jobs/presentation/entry_screen/entry_screen_controller.dart rename lib/{app/home/job_entries => src/features/jobs/presentation/job_entries_screen}/entry_list_item.dart (91%) create mode 100644 lib/src/features/jobs/presentation/job_entries_screen/job_entries_list.dart create mode 100644 lib/src/features/jobs/presentation/job_entries_screen/job_entries_list_controller.dart create mode 100644 lib/src/features/jobs/presentation/job_entries_screen/job_entries_screen.dart create mode 100644 lib/src/features/jobs/presentation/jobs_screen/jobs_screen.dart create mode 100644 lib/src/features/jobs/presentation/jobs_screen/jobs_screen_controller.dart rename lib/{services/shared_preferences_service.dart => src/features/onboarding/data/onboarding_repository.dart} (68%) create mode 100644 lib/src/features/onboarding/presentation/onboarding_controller.dart create mode 100644 lib/src/features/onboarding/presentation/onboarding_screen.dart create mode 100644 lib/src/localization/string_hardcoded.dart create mode 100644 lib/src/routing/app_router.dart create mode 100644 lib/src/routing/go_router_refresh_stream.dart create mode 100644 lib/src/routing/not_found_screen.dart create mode 100644 lib/src/routing/scaffold_with_bottom_nav_bar.dart create mode 100644 lib/src/utils/alert_dialogs.dart create mode 100644 lib/src/utils/async_value_ui.dart rename lib/{app/home/job_entries => src/utils}/format.dart (100%) create mode 100644 lib/src/utils/show_alert_dialog.dart create mode 100644 lib/src/utils/show_exception_alert_dialog.dart delete mode 100644 media/application-layers.png delete mode 100644 media/time-tracker-widget-tree.png create mode 100644 pubspec.lock delete mode 100644 test/mocks.dart delete mode 100644 test/onboarding_view_model_test.dart delete mode 100644 test/shared_preferences_service_test.dart delete mode 100644 test/sign_in_page_test.dart delete mode 100644 test/sign_in_view_model_test.dart create mode 100644 test/src/features/authentication/presentation/account/account_screen_controller_test.dart rename test/{ => src/features/jobs/domain}/job_test.dart (94%) create mode 100644 test/src/mocks.dart delete mode 100644 test_driver/app.dart delete mode 100644 test_driver/app_test.dart delete mode 100644 test_driver/fake_auth_service.dart diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 934a7a22..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: bizz84 diff --git a/media/time-tracker-screenshots.png b/.github/images/time-tracker-screenshots.png similarity index 100% rename from media/time-tracker-screenshots.png rename to .github/images/time-tracker-screenshots.png diff --git a/.gitignore b/.gitignore index e3947272..bd62e555 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Miscellaneous *.class -*.lock +#*.lock *.log *.pyc *.swp @@ -17,7 +17,7 @@ .idea/ # Visual Studio Code related -.vscode/ +#.vscode/ # Flutter repo-specific /bin/cache/ @@ -96,5 +96,9 @@ web/firebase-config.js !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages # Firebase configuration files +lib/firebase_options.dart ios/Runner/GoogleService-Info.plist -android/app/google-services.json \ No newline at end of file +ios/firebase_app_id_file.json +macos/Runner/GoogleService-Info.plist +macos/firebase_app_id_file.json +android/app/google-services.json diff --git a/.metadata b/.metadata index 1b5cec02..e773ea52 100644 --- a/.metadata +++ b/.metadata @@ -1,10 +1,30 @@ # 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. +# This file should be version controlled. version: - revision: 27321ebbad34b0a3fafe99fac037102196d655ff + revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 channel: stable project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + - platform: ios + create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + + # 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/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..6a64ca34 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,25 @@ +{ + // 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": "Run", + "request": "launch", + "type": "dart" + }, + { + "name": "Run (profile mode)", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "Run (release mode)", + "request": "launch", + "type": "dart", + "flutterMode": "release" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index cee76bfa..760f9359 100644 --- a/README.md +++ b/README.md @@ -1,339 +1,60 @@ -# Starter Architecture Demo for Flutter & Firebase Realtime Apps +# Time Tracking app with Flutter & Firebase -This is a **reference architecture demo** that can be used as a **starting point** for apps using Flutter & Firebase. +A time tracking application built with Flutter & Firebase: -*Also see my [codewithandrea_flutter_packages repo](https://github.com/bizz84/codewithandrea_flutter_packages), which contains the most reusable parts of this project as packages.* +![](/.github/images/time-tracker-screenshots.png) -## Motivation +This is intended as a **reference app** based on my [Riverpod Architecture](https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/). -Flutter & Firebase are a great combo for getting apps to market in record time. +> **Note**: this project used to be called "Started Architecture for Flutter & Firebase" (based on this [old article](https://codewithandrea.com/videos/starter-architecture-flutter-firebase/)). As of January 2023, it follows my updated [Riverpod Architecture](https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/), using the latest packages. -Without a sound architecture, codebases can quickly become hard to test, maintain, and **reason about**. This **severely** impacts the development speed, and results in buggy products, sad developers and unhappy users. +## Features -I have already witnessed this first-hand with various client projects, where the lack of a formal architecture led to days, weeks - even **months** of extra work. +- **Simple onboarding page** +- **Full authentication flow** (using email & password) +- **Jobs**: users can view, create, edit, and delete their own private jobs (each job has a name and hourly rate) +- **Entries**: for each job, user can view, create, edit, and delete the corresponding entries (an entry is a task with a start and end time, with an optional comment) +- **A report page** that shows a daily breakdown of all jobs, hours worked and pay, along with the totals. -Is "architecture" hard? How can one find the "right" or "correct" architecture in the ever-changing landscape of front-end development? +All the data is persisted with Firestore and is kept in sync across multiple devices. -Every app has different requirements, so does the "right" architecture even exist in the first place? +## Roadmap -While I don't claim to have a silver bullet, I have refined and fine-tuned a **production-ready** architecture that I have deployed successfully into multiple Flutter & Firebase apps. +- [ ] Add missing tests +- [ ] Stateful Nested Navigation with GoRouter (once [this PR](https://github.com/flutter/packages/pull/2650) is merged) +- [ ] Use controllers / notifiers consistently across the app (some code still needs to be updated) +- [ ] Add localization +- [ ] Use the new Firebase UI packages where useful +- [ ] Responsive UI -I call this "**Stream-based** Architecture for Flutter & Firebase **Realtime** Apps". +> This is a tentative roadmap. There is no ETA for any of the points above. This is a low priority project and I don't have much time to maintain it. -## Stream-based Architecture for Flutter & Firebase Realtime Apps +## Relevant Articles -Two words are key here: **Stream** and **Realtime**. +The app is based on my Flutter Riverpod architecture, which is explained in detail here: -Unlike with traditional REST APIs, with Firebase we can build **realtime** apps. +- [Flutter App Architecture with Riverpod: An Introduction](https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/) +- [Flutter Project Structure: Feature-first or Layer-first?](https://codewithandrea.com/articles/flutter-project-structure/) +- [Flutter App Architecture: The Repository Pattern](https://codewithandrea.com/articles/flutter-repository-pattern/) -That's because Firebase can **push** updates directly to **subscribed** clients when something changes. +More more info on Riverpod, read this: -For example, widgets can **rebuild** themselves when certain Firestore *documents* or *collections* are updated. +- [Flutter Riverpod 2.0: The Ultimate Guide](https://codewithandrea.com/articles/flutter-state-management-riverpod/) -Many Firebase APIs are **inherently** stream-based. As a result, the **simplest** way of making our widgets reactive is to use [`StreamProvider`](https://pub.dev/documentation/riverpod/latest/all/StreamProvider-class.html) from the [Riverpod package](https://riverpod.dev). This provides a convenient way of **watching** changes in your Firebase streams, and automatically rebuilding widgets with minimal boilerplate code. +## Packages in use -Yes, you could use [`ChangeNotifier`](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html) or other state management techniques that implement observables/listeners. But you would need additional "glue" code if you want to "convert" your input streams into reactive models based on `ChangeNotifier`. +These are the main packages used in the app: -> Note: streams are the default way of pushing changes not only with Firebase, but with many other services as well. For example, you can get location updates with the `onLocationChanged()` stream of the [location](https://pub.dev/packages/location) package. Whether you use Firestore, or want to get data from your device's input sensors, streams are the most convenient way of delivering **asynchronous** data over time. +- [Flutter Riverpod](https://pub.dev/packages/flutter_riverpod) for data caching, dependency injection, and more +- [GoRouter](https://pub.dev/packages/go_router) for navigation +- [Firebase Auth](https://pub.dev/packages/firebase_auth) for authentication +- [Cloud Firestore](https://pub.dev/packages/cloud_firestore) as a realtime database +- [RxDart](https://pub.dev/packages/rxdart) for combining multiple Firestore collections as needed +- [Intl](https://pub.dev/packages/intl) for currency, date, time formatting +- [Mocktail](https://pub.dev/packages/mocktail) for testing +- [Equatable](https://pub.dev/packages/equatable) to reduce boilerplate code in model classes -A more detailed overview of this architecture is outlined below. But first, here are the goals for this project. - -## Project Goals - -Define a reference architecture that can be used as the **foundation** for Flutter apps using Firebase (or other streaming APIs). - -This architecture should: - -- **minimize mutable state** by adopting an **unidirectional data flow** -- clearly define application layers and their boundaries -- require little boilerplate code - -The resulting code should be: - -- clear -- reusable -- scalable -- testable -- performant -- maintainable - -These are all nice properties, but how do they all fit together in practice? - -By introducing application layers with clear boundaries, and defining how the data flows through them. - -## The Application Layers - -![](media/application-layers.png) - -To ensure a good separation of concerns, this architecture defines three main application layers. - -- **UI Layer**: where the widgets live -- **Logic & Presentation Layer**: this contains the application's business and presentation logic -- **Domain Layer**: this contains domain-specific services for interacting with 3rd party APIs - -*These layers may be named differently in other literature.* - -What matters here is that the data flows from the services into the widgets, and the call flow goes in the opposite direction. - -Widgets **subscribe** themselves as listeners, while view models **publish** updates when something changes. - ---- - -The publish/subscribe pattern comes in many variants (e.g. ChangeNotifier, BLoC), and this architecture does not prescribe which one to use. - -By using Riverpod, we can easily use the most convenient pattern on a case-by-case bsis. In practice, this means using `Stream`s with `StreamProvider` when reading and manipulating data from Firestore. But when dealing with **local** application state, `StatefulWidget`+`setState` or `ChangeNotifier` are sometimes used. - -Let's look at the three application layers in more detail. - -### Domain Layer: Services - -Services are **pure**, functional components that don't hold any state. - -Services serve as an **abstraction** from external data sources, and provide domain-specific APIs to the rest of the app (more on this below). - -Because service APIs return **strongly-typed**, **immutable**, **domain-specific** model objects, the rest of the app doesn't directly manipulate the raw data from the outside world (e.g. Firestore documents represented as key-value pairs). - -As a bonus, breaking changes in external packages are easier to deal with, because they only affect the corresponding service classes. - -### Presentation & Logic Layer: View Models - -View models abstract the widgets' **state** and **presentation**. - -View models **do not have any reference** to the widgets themselves. Rather, they define an **interface** for **publishing** updates when something changes. - -View models can talk directly to service classes to read or write data, and access other domain-specific APIs. - -But unlike service classes, they can **hold and modify state**, according to some business logic. - -View models can also be used to hold **local** state. This is common when converting a `StatefulWidget` into a `StatelessWidget` - -> NOTE: View models are **completely independent from the UI**. View model classes never import Flutter code (e.g. `material.dart`) - -### UI Layer: Widgets - -Widgets are used to specify how the application UI looks like, and provide callbacks in response to user interaction. - -Strictly speaking, we can introduce a distinction: - -- **pure UI widgets**: these are the usual buttons, texts, containers -- **logic or presentational widgets**: these are used to decide what widget to return, based on some condition (e.g. to return the home page or sign page based on the authentication status of the user). - ------ - -This project contains a demo app as a practical implementation of this architecture. - -## Demo App: Time Tracker - -The demo app is a time tracking application. It is complex enough to capture the various nuances of state management across multiple features. Here is a preview of the main screens: - -![](media/time-tracker-screenshots.png) - -After signing in, users can view, create, edit and delete their jobs. For each job they can view, create, edit and delete the corresponding entries. - -A separate screen shows a daily breakdown of all jobs, hours worked and pay, along with the totals. - -All the data is persisted with Firestore, and is kept in sync across multiple devices. - -## Riverpod - -[Riverpod](https://pub.dev/packages/riverpod) is a rewrite of the popular [Provider package](https://pub.dev/packages/provider), and improves on its weaknesses. It is a natural fit for this app. - -Riverpod can be used to create **global** providers that are not tied to the widget tree, and these can then be accessed by **reference**. In this sense, Riverpod works more like a **service locator**. - -### Creating Providers with Riverpod - -For example, here are some providers that are created using Riverpod: - -```dart -// 1 -final firebaseAuthProvider = - Provider((ref) => FirebaseAuth.instance); - -// 2 -final authStateChangesProvider = StreamProvider( - (ref) => ref.watch(firebaseAuthProvider).authStateChanges()); - -// 3 -final databaseProvider = Provider((ref) { - final auth = ref.watch(authStateChangesProvider); - - // we only have a valid DB if the user is signed in - if (auth.asData?.value?.uid != null) { - return FirestoreDatabase(uid: auth.asData!.value!.uid); - } - return null; -}); -``` - -As we can see, `authStateChangesProvider` **depends** on `firebaseAuthProvider`, and can get access to it using `ref.watch`. - -Similarly, `databaseProvider` **depends** on `authStateChangesProvider`. - -One powerful feature of Riverpod is that we can **watch** a provider's value and rebuild all dependent providers and widgets **when the value changes**. - -An example of this is the `databaseProvider` above. This provider's value is rebuilt every time the `authStateChangesProvider`'s value **changes**. This is used to return either a `FirestoreDatabase` object or `null` depending on the authentication state. - -### Using Riverpod inside widgets - -Widgets can access these providers with a `ScopedReader`, either via `Consumer` or `ConsumerWidget`. - -For example, here is some sample code demonstrating how to use `StreamProvider` to read some data from a stream: - -```dart -final jobStreamProvider = - StreamProvider.autoDispose.family((ref, jobId) { - final database = ref.watch(databaseProvider)!; - return database.jobStream(jobId: jobId); -}); -``` - -In this case the `StreamProvider` can auto-dispose itself when all its listeners unsubscribe. And we're using `.family` to read a `jobId` parameter that is only known at runtime. - -Here's a widget that *watches* this `StreamProvider` and uses it to show some UI based on the stream's latest state (data available / loading / error): - -```dart -class JobEntriesAppBarTitle extends ConsumerWidget { - const JobEntriesAppBarTitle({required this.job}); - final Job job; - - @override - Widget build(BuildContext context, WidgetRef ref) { - // 1: watch changes in the stream - final jobAsyncValue = ref.watch(jobStreamProvider(job.id)); - // 2: return the correct widget depending on the stream value - return jobAsyncValue.when( - data: (job) => Text(job.name), - loading: () => Container(), - error: (_, __) => Container(), - ); - } -} -``` - -This widget class is as simple as it can be, as it only needs to **watch** for changes in the stream (step 1), and return the correct widget depending on the stream value (step 2). - -This is great because all the logic for setting up the `StreamProvider` lives inside the provider itself, and is completely separate from the UI code. - --------- - -In addition to the top-level providers and the `StreamProvider`s that read data from Firestore, Riverpod is also used to create and configure view models for widgets that require local state. - -These view models can hold any app-specific business logic, and if they're based on `ChangeNotifier` or `StateNotifier`, they can be easily hooked up to their widgets with corresponding providers. See the [SignInViewModel](https://github.com/bizz84/starter_architecture_flutter_firebase/blob/master/lib/app/sign_in/sign_in_view_model.dart) and [SignInPage](https://github.com/bizz84/starter_architecture_flutter_firebase/blob/master/lib/app/sign_in/sign_in_page.dart) widget for an example of this. - -## Project structure - -Folders are grouped by feature/page. Each feature may define its own models and view models. - -Services and routing classes are defined at the root, along with constants and common widgets shared by multiple features. - -``` -/lib - /app - /home - /account - /entries - /job_entries - /jobs - /models - /sign_in - /common_widgets - /constants - /routing - /services -``` - -This is an arbitrary structure. Choose what works best for **your** project. - -## Use Case: Firestore Service - -Widgets can subscribe to updates from Firestore data via streams. -Equally, write operations can be issued with Future-based APIs. - -Here's the entire Database API for the demo app, showing all the supported CRUD operations: - -```dart -class FirestoreDatabase { // implementation omitted for brevity - Future setJob(Job job); // create / update - Future deleteJob(Job job); // delete - Stream> jobsStream(); // read - Stream jobStream({required String jobId}); // read - - Future setEntry(Entry entry); // create / update - Future deleteEntry(Entry entry); // delete - Stream> entriesStream({Job job}); // read -} -``` - -As shown above, widgets can read these input streams via `StreamProvider`s, and use a _watch_ to reactively rebuild the UI. - -For convenience, all available collections and documents are listed in a single class: - -```dart -class APIPath { - static String job(String uid, String jobId) => 'users/$uid/jobs/$jobId'; - static String jobs(String uid) => 'users/$uid/jobs'; - static String entry(String uid, String entryId) => - 'users/$uid/entries/$entryId'; - static String entries(String uid) => 'users/$uid/entries'; -} -``` - -Domain-level model classes are defined, along with `fromMap()` and `toMap()` methods for serialization. -These classes are strongly-typed and immutable. - -See the [FirestoreDatabase](https://github.com/bizz84/starter_architecture_flutter_firebase/blob/master/lib/services/firestore_database.dart) and [FirestoreService](https://github.com/bizz84/starter_architecture_flutter_firebase/blob/master/lib/services/firestore_service.dart) classes for a full picture of how everything fits together. - -## Routing - -The app uses named routes, which are defined in a `Routes` class: - -```dart -class AppRoutes { - static const emailPasswordSignInPage = '/email-password-sign-in-page'; - static const editJobPage = '/edit-job-page'; - static const entryPage = '/entry-page'; -} -``` - -A `AppRouter` is then used to generate all the routes with a switch statement: - -```dart -class AppRouter { - static Route onGenerateRoute(RouteSettings settings) { - final args = settings.arguments; - switch (settings.name) { - // all cases here - } - } -} -``` - -Given a page that needs to be presented inside a route, we can call `pushNamed` with the name of the route, and pass all required arguments. If more than one argument is needed, we can use a map: - -```dart -class EntryPage extends ConsumerStatefulWidget { - const EntryPage({required this.job, this.entry}); - final Job job; - final Entry? entry; - - static Future show( - {required BuildContext context, required Job job, Entry? entry}) async { - await Navigator.of(context, rootNavigator: true).pushNamed( - AppRoutes.entryPage, - arguments: { - 'job': job, - 'entry': entry, - }, - ); - } - - @override - _EntryPageState createState() => _EntryPageState(); -} - -``` - -Note: previously the app was using [`auto_route`](https://pub.dev/packages/auto_route), which uses code generation to make routes **strongly-typed**. This has caused subtle issues, that took some time to investigate. So the project now uses manual routes, which are much more predictable. +See the [pubspec.yaml](pubspec.yaml) file for the complete list. ## Running the project with Firebase @@ -347,9 +68,9 @@ To use this project with Firebase, some configuration steps are required. - then, [download and copy](https://firebase.google.com/docs/flutter/setup#configure_an_ios_app) `GoogleService-Info.plist` into `iOS/Runner`, and add it to the Runner target in Xcode. - finally, enable the Email/Password Authentication Sign-in provider in the Firebase Console (Authentication > Sign-in method > Email/Password > Edit > Enable > Save) -See this page for full instructions: +To speed up the process, you can use the [FlutterFire CLI](https://pub.dev/packages/flutterfire_cli) as explained here: -- [FlutterFire Overview](https://firebase.flutter.dev/docs/overview) +- [How to add Firebase to a Flutter app with FlutterFire CLI](https://codewithandrea.com/articles/flutter-firebase-flutterfire-cli/) ## Running on Flutter Web @@ -385,35 +106,4 @@ This is then imported in the `index.html` file: ``` -## Packages - -- `firebase_auth` for authentication -- `cloud_firestore` for the remote database -- `flutter_riverpod` for state management -- `rxdart` for combining multiple Firestore collections as needed -- `intl` for currency, date, time formatting -- `mocktail` for testing -- `equatable` to reduce boilerplate code in model classes - -Also imported from my [flutter_core_packages repo](https://github.com/bizz84/flutter_core_packages): - -- `firestore_service` -- `custom_buttons` -- `alert_dialogs` -- `email_password_sign_in_ui` - -## References - -This project borrows many ideas from my [Flutter & Firebase Course](https://nnbd.me/ff), as well as my [Reference Authentication Flow with Flutter & Firebase](https://github.com/bizz84/firebase_auth_demo_flutter), and takes them to the next level by using Riverpod. - -Here are some other GitHub projects that also attempt to formalize a good approach to Flutter development: - -- [Beyond - An approach to scalable Flutter development](https://github.com/MisterJimson/beyond) -- This [starter app](https://github.com/gregertw/actingweb_firstapp) that includes many different production app features. Related articles: [A Production-Quality Flutter Starter App](https://stuff.greger.io/2019/07/production-quality-flutter-starter-app.html), and [this follow up](https://stuff.greger.io/2020/01/production-quality-flutter-starter-app-take-two.html). - -Other relevant articles about app architecture: - -- [Widget-Async-Bloc-Service: A Practical Architecture for Flutter Apps](https://codewithandrea.com/articles/2019-05-21-wabs-practical-architecture-flutter-apps/) -- [Flutter TDD Clean Architecture Course [1] – Explanation & Project Structure](https://resocoder.com/2019/08/27/flutter-tdd-clean-architecture-course-1-explanation-project-structure/) - ## [License: MIT](LICENSE.md) diff --git a/analysis_options.yaml b/analysis_options.yaml index 6332f066..61b6c4de 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -9,92 +9,21 @@ # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml -analyzer: - strong-mode: - implicit-casts: false - implicit-dynamic: false - errors: - # Otherwise cause the import of all_lint_rules to warn because of some rules conflicts. - # The conflicts are fixed in this file instead, so we can safely ignore the warning. - included_file_warning: ignore - # treat missing required parameters as a warning (not a hint) - missing_required_param: warning - # treat missing returns as a warning (not a hint) - missing_return: warning - # allow having TODOs in the code - todo: ignore - # Ignore analyzer hints for updating pubspecs when using Future or - # Stream and not importing dart:async - # Please see https://github.com/flutter/flutter/pull/24528 for details. - sdk_version_async_exported_from_core: ignore - # Custom errors to be ignored - implicit_dynamic_type: ignore - #invalid_assignment: ignore - implicit_dynamic_map_literal: ignore - always_put_control_body_on_new_line: ignore - exclude: - - "bin/cache/**" - # the following two are relative to the stocks example and the flutter package respectively - # see https://github.com/dart-lang/sdk/issues/28463 - - "lib/i18n/messages_*.dart" - - "lib/src/http/**" - 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: - # navigator.pop inside a closure requires this - unnecessary_lambdas: false - # It's ok - cascade_invocations: false - # Ignored in tests - missing_whitespace_between_adjacent_strings: false - # In the future - type_annotate_public_apis: false - # It's ok - avoid_print: false - # Not including author name in small projects - flutter_style_todos: false - # Sometimes used - avoid_as: false - # Not sure how to address this - use_raw_strings: false - # Need to fix this - comment_references: false - # Ok to use a class - one_member_abstracts: false - # Need to fix this - avoid_annotating_with_dynamic: false - # Disabled for type inference - always_specify_types: false - # non-required Key often precedes required named parameters - always_put_required_named_parameters_first: false - # Catch all often used in project - avoid_catches_without_on_clauses: false - # Ok to be explicit - avoid_redundant_argument_values: false - # Still some instances of this in the project - lines_longer_than_80_chars: false - # Often used in local variables - unnecessary_final: false - # Not always done with factory constructors - sort_constructors_first: false - # Ok to be explicit - omit_local_variable_types: false - # For build methods - prefer_expression_function_bodies: false - # I use double out of habit - prefer_int_literals: false - # sometimes using `with` syntax - prefer_mixin: false - # Codebase uses mostly single quotes - avoid_escaping_inner_quotes: false - prefer_double_quotes: false - # Need to fix this sometime (easier to copy paste files across projects) - prefer_relative_imports: false - # A lot of documentation missing - public_member_api_docs: false - # Not enforced - sort_child_properties_last: false - # Many constructors not declared for widgets that take no arguments - use_key_in_widget_constructors: false - # Disable: DO reference all public properties in debug method implementations. - diagnostic_describe_all_properties: 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 diff --git a/android/.gitignore b/android/.gitignore index bc2100d8..6f568019 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -5,3 +5,9 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle index 53a2c9e9..848684db 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -22,29 +22,38 @@ if (flutterVersionName == null) { } apply plugin: 'com.android.application' +// START: FlutterFire Configuration +apply plugin: 'com.google.gms.google-services' +// END: FlutterFire Configuration apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 29 + compileSdkVersion localProperties.getProperty('flutter.compileSdkVersion').toInteger() + ndkVersion flutter.ndkVersion - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } - lintOptions { - disable 'InvalidPackage' + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.starter_architecture_flutter_firebase" - minSdkVersion 21 - targetSdkVersion 29 + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion localProperties.getProperty('flutter.minSdkVersion').toInteger() + targetSdkVersion localProperties.getProperty('flutter.targetSdkVersion').toInteger() versionCode flutterVersionCode.toInteger() versionName flutterVersionName - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - multiDexEnabled true } buildTypes { @@ -62,10 +71,4 @@ flutter { 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' - implementation 'com.android.support:multidex:1.0.3' } - -apply plugin: 'com.google.gms.google-services' diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index b1cd231c..56ddc3b2 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,7 @@ - diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4875fc57..dbb58b1a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,21 +1,25 @@ - - + + diff --git a/android/app/src/main/kotlin/com/example/starter_architecture_flutter_firebase/MainActivity.kt b/android/app/src/main/kotlin/com/example/starter_architecture_flutter_firebase/MainActivity.kt index 6c5f8a08..057eaaf7 100644 --- a/android/app/src/main/kotlin/com/example/starter_architecture_flutter_firebase/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/starter_architecture_flutter_firebase/MainActivity.kt @@ -1,12 +1,6 @@ package com.example.starter_architecture_flutter_firebase -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/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 00fa4417..cb1ef880 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,8 +1,18 @@ - + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index b1cd231c..56ddc3b2 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,6 +1,7 @@ - diff --git a/android/build.gradle b/android/build.gradle index aafab52b..9192654d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,13 +1,15 @@ buildscript { - ext.kotlin_version = '1.5.0' + ext.kotlin_version = '1.6.10' repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - classpath 'com.google.gms:google-services:4.0.1' + classpath 'com.android.tools.build:gradle:7.1.2' + // START: FlutterFire Configuration + classpath 'com.google.gms:google-services:4.3.10' + // END: FlutterFire Configuration classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -15,7 +17,7 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/android/gradle.properties b/android/gradle.properties index 38c8d454..94adc3a3 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,3 @@ org.gradle.jvmargs=-Xmx1536M -android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 939efa29..cb24abda 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#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-6.7.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 5a2f14fb..44e62bcf 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,15 +1,11 @@ include ':app' -def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() -def plugins = new Properties() -def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') -if (pluginsFile.exists()) { - pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } -} +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory -} +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/ios/.gitignore b/ios/.gitignore index e96ef602..7a7f9873 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index f2872cf4..9625e105 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 11.0 diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index e8efba11..ec97fc6f 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1,2 +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" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 399e9340..c4855bfe 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1,2 +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" diff --git a/ios/Podfile b/ios/Podfile index 6a824b1e..7a5c87ef 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '10.0' +platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -28,18 +28,32 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do + pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '10.1.0' use_frameworks! use_modular_headers! - # Use this to speed things up with precompiled binary. - # More info here: https://github.com/invertase/firestore-ios-sdk-frameworks - #pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '7.3.0' - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| + # Ensure pods also use the minimum deployment target set above + # https://stackoverflow.com/a/64385584/436422 + puts 'Determining pod project minimum deployment target' + + pods_project = installer.pods_project + deployment_target_key = 'IPHONEOS_DEPLOYMENT_TARGET' + deployment_targets = pods_project.build_configurations.map{ |config| config.build_settings[deployment_target_key] } + minimum_deployment_target = deployment_targets.min_by{ |version| Gem::Version.new(version) } + + puts 'Minimal deployment target is ' + minimum_deployment_target + puts 'Setting each pod deployment target to ' + minimum_deployment_target + installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + #config.build_settings['ENABLE_BITCODE'] = 'NO' + config.build_settings[deployment_target_key] = minimum_deployment_target + #config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0' + end end end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 00000000..f933b3c6 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,124 @@ +PODS: + - cloud_firestore (4.0.5): + - Firebase/Firestore (= 10.1.0) + - firebase_core + - Flutter + - nanopb (< 2.30910.0, >= 2.30908.0) + - Firebase/Auth (10.1.0): + - Firebase/CoreOnly + - FirebaseAuth (~> 10.1.0) + - Firebase/CoreOnly (10.1.0): + - FirebaseCore (= 10.1.0) + - Firebase/Firestore (10.1.0): + - Firebase/CoreOnly + - FirebaseFirestore (~> 10.1.0) + - firebase_auth (4.1.2): + - Firebase/Auth (= 10.1.0) + - firebase_core + - Flutter + - firebase_core (2.2.0): + - Firebase/CoreOnly (= 10.1.0) + - Flutter + - FirebaseAuth (10.1.0): + - FirebaseCore (~> 10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.8) + - GoogleUtilities/Environment (~> 7.8) + - GTMSessionFetcher/Core (~> 2.1) + - FirebaseCore (10.1.0): + - FirebaseCoreInternal (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - GoogleUtilities/Logger (~> 7.8) + - FirebaseCoreInternal (10.1.0): + - "GoogleUtilities/NSData+zlib (~> 7.8)" + - FirebaseFirestore (10.1.0): + - FirebaseFirestore/AutodetectLeveldb (= 10.1.0) + - FirebaseFirestore/AutodetectLeveldb (10.1.0): + - FirebaseFirestore/Base + - FirebaseFirestore/WithLeveldb + - FirebaseFirestore/Base (10.1.0) + - FirebaseFirestore/WithLeveldb (10.1.0): + - FirebaseFirestore/Base + - Flutter (1.0.0) + - GoogleUtilities/AppDelegateSwizzler (7.10.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (7.10.0): + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/Logger (7.10.0): + - GoogleUtilities/Environment + - GoogleUtilities/Network (7.10.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (7.10.0)" + - GoogleUtilities/Reachability (7.10.0): + - GoogleUtilities/Logger + - GTMSessionFetcher/Core (2.3.0) + - nanopb (2.30909.0): + - nanopb/decode (= 2.30909.0) + - nanopb/encode (= 2.30909.0) + - nanopb/decode (2.30909.0) + - nanopb/encode (2.30909.0) + - PromisesObjC (2.1.1) + - shared_preferences_ios (0.0.1): + - Flutter + +DEPENDENCIES: + - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) + - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - FirebaseFirestore (from `https://github.com/invertase/firestore-ios-sdk-frameworks.git`, tag `10.1.0`) + - Flutter (from `Flutter`) + - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) + +SPEC REPOS: + trunk: + - Firebase + - FirebaseAuth + - FirebaseCore + - FirebaseCoreInternal + - GoogleUtilities + - GTMSessionFetcher + - nanopb + - PromisesObjC + +EXTERNAL SOURCES: + cloud_firestore: + :path: ".symlinks/plugins/cloud_firestore/ios" + firebase_auth: + :path: ".symlinks/plugins/firebase_auth/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + FirebaseFirestore: + :git: https://github.com/invertase/firestore-ios-sdk-frameworks.git + :tag: 10.1.0 + Flutter: + :path: Flutter + shared_preferences_ios: + :path: ".symlinks/plugins/shared_preferences_ios/ios" + +CHECKOUT OPTIONS: + FirebaseFirestore: + :git: https://github.com/invertase/firestore-ios-sdk-frameworks.git + :tag: 10.1.0 + +SPEC CHECKSUMS: + cloud_firestore: 345ab5f423db6ae492abb648372156bdccb9df42 + Firebase: 444b35a9c568a516666213c2f6cccd10cb12559f + firebase_auth: 6e24814d3c9976a288da6bb7a49ac79616863b5b + firebase_core: d2242c6f318db1d0dcecfbfa491e943337b0d755 + FirebaseAuth: 19a85b8a42e7c1104a2ffa6987c748daa79a5e64 + FirebaseCore: 55e7ae35991ccca4db03ff8d8df6ed5f17a3e4c7 + FirebaseCoreInternal: 96d75228e10fd369564da51bd898414eb0f54df5 + FirebaseFirestore: c55b29bb38afeed2fe73c241e55c7f8230ba7abc + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + GoogleUtilities: bad72cb363809015b1f7f19beb1f1cd23c589f95 + GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2 + nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431 + PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb + shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad + +PODFILE CHECKSUM: 21ac9d4574524fb5f3601d1a026bd1e110fc75b1 + +COCOAPODS: 1.11.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 453add67..2e9119d3 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,18 +3,18 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; 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 */; }; - 6543D66123DC4D96003E597A /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6543D66023DC4D96003E597A /* GoogleService-Info.plist */; }; - 6F5D4EBB0FC7186A260B9736 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92EBAE997B345C97CC736C63 /* Pods_Runner.framework */; }; 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 */; }; + FDFBBE3698D6DEE111B2295A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BE5E729672CE82DA371BE171 /* Pods_Runner.framework */; }; + FEBDC9609C680EC02937034B /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D4DF1EAB0E660ABB5816D63F /* GoogleService-Info.plist */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -34,12 +34,10 @@ 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 = ""; }; - 6543D66023DC4D96003E597A /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 4717B7480637AA09CAEB3FC0 /* 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 = ""; }; 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 = ""; }; - 74B49E421CD42F3C6750BD3F /* 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; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 92EBAE997B345C97CC736C63 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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; }; @@ -47,8 +45,10 @@ 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 = ""; }; - B806766DCEDA019CE6F53E9B /* 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 = ""; }; - BF5302CB579B37E0835290F6 /* 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 = ""; }; + B1282A8CCA46FCB860DC6348 /* 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 = ""; }; + BE5E729672CE82DA371BE171 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D1CF56C09A84454F93ABC256 /* 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 = ""; }; + D4DF1EAB0E660ABB5816D63F /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -56,7 +56,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6F5D4EBB0FC7186A260B9736 /* Pods_Runner.framework in Frameworks */, + FDFBBE3698D6DEE111B2295A /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -80,8 +80,9 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 9A5F0FFF436695CFCF6AB113 /* Pods */, - B63212B0E8BDEC5B71B9B990 /* Frameworks */, + D4DF1EAB0E660ABB5816D63F /* GoogleService-Info.plist */, + F90C4DD405ACFD15095CAEB7 /* Pods */, + BB5370A4696621AC47A50471 /* Frameworks */, ); sourceTree = ""; }; @@ -100,8 +101,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 6543D66023DC4D96003E597A /* GoogleService-Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, @@ -110,31 +109,25 @@ path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { + BB5370A4696621AC47A50471 /* Frameworks */ = { isa = PBXGroup; children = ( + BE5E729672CE82DA371BE171 /* Pods_Runner.framework */, ); - name = "Supporting Files"; + name = Frameworks; sourceTree = ""; }; - 9A5F0FFF436695CFCF6AB113 /* Pods */ = { + F90C4DD405ACFD15095CAEB7 /* Pods */ = { isa = PBXGroup; children = ( - B806766DCEDA019CE6F53E9B /* Pods-Runner.debug.xcconfig */, - 74B49E421CD42F3C6750BD3F /* Pods-Runner.release.xcconfig */, - BF5302CB579B37E0835290F6 /* Pods-Runner.profile.xcconfig */, + B1282A8CCA46FCB860DC6348 /* Pods-Runner.debug.xcconfig */, + 4717B7480637AA09CAEB3FC0 /* Pods-Runner.release.xcconfig */, + D1CF56C09A84454F93ABC256 /* Pods-Runner.profile.xcconfig */, ); + name = Pods; path = Pods; sourceTree = ""; }; - B63212B0E8BDEC5B71B9B990 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 92EBAE997B345C97CC736C63 /* Pods_Runner.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -142,14 +135,15 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 2565013367BDC3DBCCAF82FD /* [CP] Check Pods Manifest.lock */, + 703CB65CA5A94374E71D4B9A /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 2B7B18995DEAAA40F7376ED3 /* [CP] Embed Pods Frameworks */, + 48CDBEA72553743FA74C2372 /* [CP] Embed Pods Frameworks */, + 3FB93D246001D3A79E3BB749 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -166,8 +160,8 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; - ORGANIZATIONNAME = "The Chromium Authors"; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; @@ -176,7 +170,7 @@ }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -200,96 +194,84 @@ files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 6543D66123DC4D96003E597A /* GoogleService-Info.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + FEBDC9609C680EC02937034B /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 2565013367BDC3DBCCAF82FD /* [CP] Check Pods Manifest.lock */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( ); + name = "Thin Binary"; 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"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 3FB93D246001D3A79E3BB749 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 2B7B18995DEAAA40F7376ED3 /* [CP] Embed Pods Frameworks */ = { + 48CDBEA72553743FA74C2372 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/BoringSSL-GRPC/openssl_grpc.framework", - "${BUILT_PRODUCTS_DIR}/FirebaseAuth/FirebaseAuth.framework", - "${BUILT_PRODUCTS_DIR}/FirebaseCore/FirebaseCore.framework", - "${BUILT_PRODUCTS_DIR}/FirebaseCoreDiagnostics/FirebaseCoreDiagnostics.framework", - "${BUILT_PRODUCTS_DIR}/FirebaseFirestore/FirebaseFirestore.framework", - "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework", - "${BUILT_PRODUCTS_DIR}/GoogleDataTransport/GoogleDataTransport.framework", - "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", - "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", - "${BUILT_PRODUCTS_DIR}/abseil/absl.framework", - "${BUILT_PRODUCTS_DIR}/gRPC-C++/grpcpp.framework", - "${BUILT_PRODUCTS_DIR}/gRPC-Core/grpc.framework", - "${BUILT_PRODUCTS_DIR}/leveldb-library/leveldb.framework", - "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", - "${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework", + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl_grpc.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseAuth.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCore.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseCoreDiagnostics.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FirebaseFirestore.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.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}/absl.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/grpcpp.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/grpc.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework", + 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; }; - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + 703CB65CA5A94374E71D4B9A /* [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 = ( ); - name = "Thin Binary"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + 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; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; @@ -380,7 +362,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -396,17 +378,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = M54ZVB688G; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.starterArchitectureFlutterFirebase; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -463,7 +440,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -512,11 +489,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -529,17 +507,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = M54ZVB688G; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.starterArchitectureFlutterFirebase; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -557,17 +530,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = M54ZVB688G; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = com.example.starterArchitectureFlutterFirebase; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cf..c87d15a3 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - + + - - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Starter Architecture Flutter Firebase CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -41,5 +43,9 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h index 7335fdf9..308a2a56 100644 --- a/ios/Runner/Runner-Bridging-Header.h +++ b/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" \ No newline at end of file +#import "GeneratedPluginRegistrant.h" diff --git a/lib/app/auth_widget.dart b/lib/app/auth_widget.dart deleted file mode 100644 index 7aaeecd8..00000000 --- a/lib/app/auth_widget.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/jobs/empty_content.dart'; -import 'package:starter_architecture_flutter_firebase/app/top_level_providers.dart'; - -class AuthWidget extends ConsumerWidget { - const AuthWidget({ - Key? key, - required this.signedInBuilder, - required this.nonSignedInBuilder, - }) : super(key: key); - final WidgetBuilder nonSignedInBuilder; - final WidgetBuilder signedInBuilder; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final authStateChanges = ref.watch(authStateChangesProvider); - return authStateChanges.when( - data: (user) => _data(context, user), - loading: () => const Scaffold( - body: Center( - child: CircularProgressIndicator(), - ), - ), - error: (_, __) => const Scaffold( - body: EmptyContent( - title: 'Something went wrong', - message: 'Can\'t load data right now.', - ), - ), - ); - } - - Widget _data(BuildContext context, User? user) { - if (user != null) { - return signedInBuilder(context); - } - return nonSignedInBuilder(context); - } -} diff --git a/lib/app/home/account/account_page.dart b/lib/app/home/account/account_page.dart deleted file mode 100644 index 445d49a5..00000000 --- a/lib/app/home/account/account_page.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'dart:async'; - -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:starter_architecture_flutter_firebase/app/top_level_providers.dart'; -import 'package:starter_architecture_flutter_firebase/common_widgets/avatar.dart'; -import 'package:alert_dialogs/alert_dialogs.dart'; -import 'package:starter_architecture_flutter_firebase/constants/keys.dart'; -import 'package:starter_architecture_flutter_firebase/constants/strings.dart'; -import 'package:flutter/material.dart'; -import 'package:pedantic/pedantic.dart'; - -class AccountPage extends ConsumerWidget { - Future _signOut(BuildContext context, FirebaseAuth firebaseAuth) async { - try { - await firebaseAuth.signOut(); - } catch (e) { - unawaited(showExceptionAlertDialog( - context: context, - title: Strings.logoutFailed, - exception: e, - )); - } - } - - Future _confirmSignOut( - BuildContext context, FirebaseAuth firebaseAuth) async { - final bool didRequestSignOut = await showAlertDialog( - context: context, - title: Strings.logout, - content: Strings.logoutAreYouSure, - cancelActionText: Strings.cancel, - defaultActionText: Strings.logout, - ) ?? - false; - if (didRequestSignOut == true) { - await _signOut(context, firebaseAuth); - } - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - final firebaseAuth = ref.watch(firebaseAuthProvider); - final user = firebaseAuth.currentUser!; - return Scaffold( - appBar: AppBar( - title: const Text(Strings.accountPage), - actions: [ - TextButton( - key: const Key(Keys.logout), - child: const Text( - Strings.logout, - style: TextStyle( - fontSize: 18.0, - color: Colors.white, - ), - ), - onPressed: () => _confirmSignOut(context, firebaseAuth), - ), - ], - bottom: PreferredSize( - preferredSize: const Size.fromHeight(130.0), - child: _buildUserInfo(user), - ), - ), - ); - } - - Widget _buildUserInfo(User user) { - return Column( - children: [ - Avatar( - photoUrl: user.photoURL, - radius: 50, - borderColor: Colors.black54, - borderWidth: 2.0, - ), - const SizedBox(height: 8), - if (user.displayName != null) - Text( - user.displayName!, - style: const TextStyle(color: Colors.white), - ), - const SizedBox(height: 8), - ], - ); - } -} diff --git a/lib/app/home/cupertino_home_scaffold.dart b/lib/app/home/cupertino_home_scaffold.dart deleted file mode 100644 index 1215a26b..00000000 --- a/lib/app/home/cupertino_home_scaffold.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/tab_item.dart'; -import 'package:starter_architecture_flutter_firebase/constants/keys.dart'; -import 'package:starter_architecture_flutter_firebase/routing/cupertino_tab_view_router.dart'; - -@immutable -class CupertinoHomeScaffold extends StatelessWidget { - const CupertinoHomeScaffold({ - Key? key, - required this.currentTab, - required this.onSelectTab, - required this.widgetBuilders, - required this.navigatorKeys, - }) : super(key: key); - - final TabItem currentTab; - final ValueChanged onSelectTab; - final Map widgetBuilders; - final Map> navigatorKeys; - - @override - Widget build(BuildContext context) { - return CupertinoTabScaffold( - tabBar: CupertinoTabBar( - key: const Key(Keys.tabBar), - currentIndex: currentTab.index, - items: [ - _buildItem(TabItem.jobs), - _buildItem(TabItem.entries), - _buildItem(TabItem.account), - ], - onTap: (index) => onSelectTab(TabItem.values[index]), - activeColor: Colors.indigo, - ), - tabBuilder: (context, index) { - final item = TabItem.values[index]; - return CupertinoTabView( - navigatorKey: navigatorKeys[item], - builder: (context) => widgetBuilders[item]!(context), - onGenerateRoute: CupertinoTabViewRouter.generateRoute, - ); - }, - ); - } - - BottomNavigationBarItem _buildItem(TabItem tabItem) { - final itemData = TabItemData.allTabs[tabItem]!; - return BottomNavigationBarItem( - icon: Icon(itemData.icon), - label: itemData.title, - ); - } -} diff --git a/lib/app/home/entries/entries_list_tile.dart b/lib/app/home/entries/entries_list_tile.dart deleted file mode 100644 index 419d0da7..00000000 --- a/lib/app/home/entries/entries_list_tile.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter/material.dart'; - -class EntriesListTileModel { - const EntriesListTileModel({ - required this.leadingText, - required this.trailingText, - this.middleText, - this.isHeader = false, - }); - final String leadingText; - final String trailingText; - final String? middleText; - final bool isHeader; -} - -class EntriesListTile extends StatelessWidget { - const EntriesListTile({required this.model}); - final EntriesListTileModel model; - - @override - Widget build(BuildContext context) { - const fontSize = 16.0; - return Container( - color: model.isHeader ? Colors.indigo[100] : null, - padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), - child: Row( - children: [ - Text(model.leadingText, style: const TextStyle(fontSize: fontSize)), - Expanded(child: Container()), - if (model.middleText != null) - Text( - model.middleText!, - style: TextStyle(color: Colors.green[700], fontSize: fontSize), - textAlign: TextAlign.right, - ), - SizedBox( - width: 60.0, - child: Text( - model.trailingText, - style: const TextStyle(fontSize: fontSize), - textAlign: TextAlign.right, - ), - ), - ], - ), - ); - } -} diff --git a/lib/app/home/entries/entries_page.dart b/lib/app/home/entries/entries_page.dart deleted file mode 100644 index 944e686b..00000000 --- a/lib/app/home/entries/entries_page.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/entries/entries_view_model.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/entries/entries_list_tile.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/jobs/list_items_builder.dart'; -import 'package:starter_architecture_flutter_firebase/app/top_level_providers.dart'; -import 'package:starter_architecture_flutter_firebase/constants/strings.dart'; - -final entriesTileModelStreamProvider = - StreamProvider.autoDispose>( - (ref) { - final database = ref.watch(databaseProvider)!; - final vm = EntriesViewModel(database: database); - return vm.entriesTileModelStream; - }, -); - -class EntriesPage extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - final entriesTileModelStream = ref.watch(entriesTileModelStreamProvider); - return Scaffold( - appBar: AppBar( - title: const Text(Strings.entries), - elevation: 2.0, - ), - body: ListItemsBuilder( - data: entriesTileModelStream, - itemBuilder: (context, model) => EntriesListTile(model: model), - ), - ); - } -} diff --git a/lib/app/home/entries/entries_view_model.dart b/lib/app/home/entries/entries_view_model.dart deleted file mode 100644 index 36c31a7c..00000000 --- a/lib/app/home/entries/entries_view_model.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:rxdart/rxdart.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/entries/daily_jobs_details.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/entries/entries_list_tile.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/entries/entry_job.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/job_entries/format.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/entry.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/job.dart'; -import 'package:starter_architecture_flutter_firebase/services/firestore_database.dart'; - -class EntriesViewModel { - EntriesViewModel({required this.database}); - final FirestoreDatabase database; - - /// combine List, List into List - Stream> get _allEntriesStream => CombineLatestStream.combine2( - database.entriesStream(), - database.jobsStream(), - _entriesJobsCombiner, - ); - - static List _entriesJobsCombiner( - List entries, List jobs) { - return entries.map((entry) { - final job = jobs.firstWhere((job) => job.id == entry.jobId); - return EntryJob(entry, job); - }).toList(); - } - - /// Output stream - Stream> get entriesTileModelStream => - _allEntriesStream.map(_createModels); - - static List _createModels(List allEntries) { - if (allEntries.isEmpty) { - return []; - } - final allDailyJobsDetails = DailyJobsDetails.all(allEntries); - - // total duration across all jobs - final totalDuration = allDailyJobsDetails - .map((dateJobsDuration) => dateJobsDuration.duration) - .reduce((value, element) => value + element); - - // total pay across all jobs - final totalPay = allDailyJobsDetails - .map((dateJobsDuration) => dateJobsDuration.pay) - .reduce((value, element) => value + element); - - return [ - EntriesListTileModel( - leadingText: 'All Entries', - middleText: Format.currency(totalPay), - trailingText: Format.hours(totalDuration), - ), - for (DailyJobsDetails dailyJobsDetails in allDailyJobsDetails) ...[ - EntriesListTileModel( - isHeader: true, - leadingText: Format.date(dailyJobsDetails.date), - middleText: Format.currency(dailyJobsDetails.pay), - trailingText: Format.hours(dailyJobsDetails.duration), - ), - for (JobDetails jobDuration in dailyJobsDetails.jobsDetails) - EntriesListTileModel( - leadingText: jobDuration.name, - middleText: Format.currency(jobDuration.pay), - trailingText: Format.hours(jobDuration.durationInHours), - ), - ] - ]; - } -} diff --git a/lib/app/home/entries/entry_job.dart b/lib/app/home/entries/entry_job.dart deleted file mode 100644 index 6ad7d19c..00000000 --- a/lib/app/home/entries/entry_job.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:starter_architecture_flutter_firebase/app/home/models/entry.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/job.dart'; - -class EntryJob { - EntryJob(this.entry, this.job); - - final Entry entry; - final Job job; -} diff --git a/lib/app/home/home_page.dart b/lib/app/home/home_page.dart deleted file mode 100644 index ce87865c..00000000 --- a/lib/app/home/home_page.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/account/account_page.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/cupertino_home_scaffold.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/entries/entries_page.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/jobs/jobs_page.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/tab_item.dart'; - -class HomePage extends StatefulWidget { - @override - _HomePageState createState() => _HomePageState(); -} - -class _HomePageState extends State { - TabItem _currentTab = TabItem.jobs; - - final Map> navigatorKeys = { - TabItem.jobs: GlobalKey(), - TabItem.entries: GlobalKey(), - TabItem.account: GlobalKey(), - }; - - Map get widgetBuilders { - return { - TabItem.jobs: (_) => JobsPage(), - TabItem.entries: (_) => EntriesPage(), - TabItem.account: (_) => AccountPage(), - }; - } - - void _select(TabItem tabItem) { - if (tabItem == _currentTab) { - // pop to first route - navigatorKeys[tabItem]!.currentState?.popUntil((route) => route.isFirst); - } else { - setState(() => _currentTab = tabItem); - } - } - - @override - Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async => - !(await navigatorKeys[_currentTab]!.currentState?.maybePop() ?? - false), - child: CupertinoHomeScaffold( - currentTab: _currentTab, - onSelectTab: _select, - widgetBuilders: widgetBuilders, - navigatorKeys: navigatorKeys, - ), - ); - } -} diff --git a/lib/app/home/job_entries/job_entries_page.dart b/lib/app/home/job_entries/job_entries_page.dart deleted file mode 100644 index ebad475d..00000000 --- a/lib/app/home/job_entries/job_entries_page.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'dart:async'; - -import 'package:alert_dialogs/alert_dialogs.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:pedantic/pedantic.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/job_entries/entry_list_item.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/job_entries/entry_page.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/jobs/edit_job_page.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/jobs/list_items_builder.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/entry.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/job.dart'; -import 'package:starter_architecture_flutter_firebase/app/top_level_providers.dart'; -import 'package:starter_architecture_flutter_firebase/routing/cupertino_tab_view_router.dart'; - -class JobEntriesPage extends StatelessWidget { - const JobEntriesPage({required this.job}); - final Job job; - - static Future show(BuildContext context, Job job) async { - await Navigator.of(context).pushNamed( - CupertinoTabViewRoutes.jobEntriesPage, - arguments: job, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - elevation: 2.0, - title: JobEntriesAppBarTitle(job: job), - centerTitle: true, - actions: [ - IconButton( - icon: const Icon(Icons.edit, color: Colors.white), - onPressed: () => EditJobPage.show( - context, - job: job, - ), - ), - IconButton( - icon: const Icon(Icons.add, color: Colors.white), - onPressed: () => EntryPage.show( - context: context, - job: job, - ), - ), - ], - ), - body: JobEntriesContents(job: job), - ); - } -} - -final jobStreamProvider = - StreamProvider.autoDispose.family((ref, jobId) { - final database = ref.watch(databaseProvider)!; - return database.jobStream(jobId: jobId); -}); - -class JobEntriesAppBarTitle extends ConsumerWidget { - const JobEntriesAppBarTitle({required this.job}); - final Job job; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final jobAsyncValue = ref.watch(jobStreamProvider(job.id)); - return jobAsyncValue.when( - data: (job) => Text(job.name), - loading: () => Container(), - error: (_, __) => Container(), - ); - } -} - -final jobEntriesStreamProvider = - StreamProvider.autoDispose.family, Job>((ref, job) { - final database = ref.watch(databaseProvider)!; - return database.entriesStream(job: job); -}); - -class JobEntriesContents extends ConsumerWidget { - final Job job; - const JobEntriesContents({required this.job}); - - Future _deleteEntry( - BuildContext context, WidgetRef ref, Entry entry) async { - try { - final database = ref.read(databaseProvider)!; - await database.deleteEntry(entry); - } catch (e) { - unawaited(showExceptionAlertDialog( - context: context, - title: 'Operation failed', - exception: e, - )); - } - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - final entriesStream = ref.watch(jobEntriesStreamProvider(job)); - return ListItemsBuilder( - data: entriesStream, - itemBuilder: (context, entry) { - return DismissibleEntryListItem( - dismissibleKey: Key('entry-${entry.id}'), - entry: entry, - job: job, - onDismissed: () => _deleteEntry(context, ref, entry), - onTap: () => EntryPage.show( - context: context, - job: job, - entry: entry, - ), - ); - }, - ); - } -} diff --git a/lib/app/home/jobs/job_list_tile.dart b/lib/app/home/jobs/job_list_tile.dart deleted file mode 100644 index 577641e3..00000000 --- a/lib/app/home/jobs/job_list_tile.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/job.dart'; - -class JobListTile extends StatelessWidget { - const JobListTile({Key? key, required this.job, this.onTap}) - : super(key: key); - final Job job; - final VoidCallback? onTap; - - @override - Widget build(BuildContext context) { - return ListTile( - title: Text(job.name), - trailing: const Icon(Icons.chevron_right), - onTap: onTap, - ); - } -} diff --git a/lib/app/home/jobs/jobs_page.dart b/lib/app/home/jobs/jobs_page.dart deleted file mode 100644 index e4ca59c4..00000000 --- a/lib/app/home/jobs/jobs_page.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/job_entries/job_entries_page.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/jobs/edit_job_page.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/jobs/job_list_tile.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/jobs/list_items_builder.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/job.dart'; -import 'package:alert_dialogs/alert_dialogs.dart'; -import 'package:starter_architecture_flutter_firebase/app/top_level_providers.dart'; -import 'package:starter_architecture_flutter_firebase/constants/strings.dart'; -import 'package:pedantic/pedantic.dart'; -import 'package:starter_architecture_flutter_firebase/services/firestore_database.dart'; - -final jobsStreamProvider = StreamProvider.autoDispose>((ref) { - final database = ref.watch(databaseProvider)!; - return database.jobsStream(); -}); - -// watch database -class JobsPage extends ConsumerWidget { - Future _delete(BuildContext context, WidgetRef ref, Job job) async { - try { - final database = ref.read(databaseProvider)!; - await database.deleteJob(job); - } catch (e) { - unawaited(showExceptionAlertDialog( - context: context, - title: 'Operation failed', - exception: e, - )); - } - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - return Scaffold( - appBar: AppBar( - title: const Text(Strings.jobs), - actions: [ - IconButton( - icon: const Icon(Icons.add, color: Colors.white), - onPressed: () => EditJobPage.show(context), - ), - ], - ), - body: _buildContents(context, ref), - ); - } - - Widget _buildContents(BuildContext context, WidgetRef ref) { - final jobsAsyncValue = ref.watch(jobsStreamProvider); - return ListItemsBuilder( - data: jobsAsyncValue, - itemBuilder: (context, job) => Dismissible( - key: Key('job-${job.id}'), - background: Container(color: Colors.red), - direction: DismissDirection.endToStart, - onDismissed: (direction) => _delete(context, ref, job), - child: JobListTile( - job: job, - onTap: () => JobEntriesPage.show(context, job), - ), - ), - ); - } -} diff --git a/lib/app/home/tab_item.dart b/lib/app/home/tab_item.dart deleted file mode 100644 index 722410ad..00000000 --- a/lib/app/home/tab_item.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:starter_architecture_flutter_firebase/constants/keys.dart'; -import 'package:starter_architecture_flutter_firebase/constants/strings.dart'; - -enum TabItem { jobs, entries, account } - -class TabItemData { - const TabItemData( - {required this.key, required this.title, required this.icon}); - - final String key; - final String title; - final IconData icon; - - static const Map allTabs = { - TabItem.jobs: TabItemData( - key: Keys.jobsTab, - title: Strings.jobs, - icon: Icons.work, - ), - TabItem.entries: TabItemData( - key: Keys.entriesTab, - title: Strings.entries, - icon: Icons.view_headline, - ), - TabItem.account: TabItemData( - key: Keys.accountTab, - title: Strings.account, - icon: Icons.person, - ), - }; -} diff --git a/lib/app/onboarding/onboarding_page.dart b/lib/app/onboarding/onboarding_page.dart deleted file mode 100644 index 0c277ba1..00000000 --- a/lib/app/onboarding/onboarding_page.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:custom_buttons/custom_buttons.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:starter_architecture_flutter_firebase/app/onboarding/onboarding_view_model.dart'; - -class OnboardingPage extends ConsumerWidget { - Future onGetStarted(BuildContext context, WidgetRef ref) async { - final onboardingViewModel = ref.read(onboardingViewModelProvider.notifier); - await onboardingViewModel.completeOnboarding(); - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - return Scaffold( - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - 'Track your time.\nBecause time counts.', - style: Theme.of(context).textTheme.headline4, - textAlign: TextAlign.center, - ), - FractionallySizedBox( - widthFactor: 0.5, - child: SvgPicture.asset('assets/time-tracking.svg', - semanticsLabel: 'Time tracking logo'), - ), - CustomRaisedButton( - onPressed: () => onGetStarted(context, ref), - color: Colors.indigo, - borderRadius: 30, - child: Text( - 'Get Started', - style: Theme.of(context) - .textTheme - .headline5! - .copyWith(color: Colors.white), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/app/onboarding/onboarding_view_model.dart b/lib/app/onboarding/onboarding_view_model.dart deleted file mode 100644 index 6192fdee..00000000 --- a/lib/app/onboarding/onboarding_view_model.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:starter_architecture_flutter_firebase/services/shared_preferences_service.dart'; -import 'package:state_notifier/state_notifier.dart'; - -final onboardingViewModelProvider = - StateNotifierProvider((ref) { - final sharedPreferencesService = ref.watch(sharedPreferencesServiceProvider); - return OnboardingViewModel(sharedPreferencesService); -}); - -class OnboardingViewModel extends StateNotifier { - OnboardingViewModel(this.sharedPreferencesService) - : super(sharedPreferencesService.isOnboardingComplete()); - final SharedPreferencesService sharedPreferencesService; - - Future completeOnboarding() async { - await sharedPreferencesService.setOnboardingComplete(); - state = true; - } - - bool get isOnboardingComplete => state; -} diff --git a/lib/app/sign_in/sign_in_button.dart b/lib/app/sign_in/sign_in_button.dart deleted file mode 100644 index eee9636a..00000000 --- a/lib/app/sign_in/sign_in_button.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:custom_buttons/custom_buttons.dart'; -import 'package:flutter/material.dart'; - -class SignInButton extends CustomRaisedButton { - SignInButton({ - Key? key, - required String text, - required Color color, - VoidCallback? onPressed, - Color textColor = Colors.black87, - double height = 50.0, - }) : super( - key: key, - child: Text(text, style: TextStyle(color: textColor, fontSize: 16.0)), - color: color, - textColor: textColor, - height: height, - onPressed: onPressed, - ); -} diff --git a/lib/app/sign_in/sign_in_page.dart b/lib/app/sign_in/sign_in_page.dart deleted file mode 100644 index d82085f8..00000000 --- a/lib/app/sign_in/sign_in_page.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'dart:math'; - -import 'package:alert_dialogs/alert_dialogs.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:starter_architecture_flutter_firebase/app/top_level_providers.dart'; -import 'package:starter_architecture_flutter_firebase/app/sign_in/sign_in_view_model.dart'; -import 'package:starter_architecture_flutter_firebase/app/sign_in/sign_in_button.dart'; -import 'package:starter_architecture_flutter_firebase/constants/keys.dart'; -import 'package:starter_architecture_flutter_firebase/constants/strings.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:starter_architecture_flutter_firebase/routing/app_router.dart'; - -final signInModelProvider = ChangeNotifierProvider( - (ref) => SignInViewModel(auth: ref.watch(firebaseAuthProvider)), -); - -class SignInPage extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - final signInModel = ref.watch(signInModelProvider); - ref.listen(signInModelProvider, (_, model) async { - if (model.error != null) { - await showExceptionAlertDialog( - context: context, - title: Strings.signInFailed, - exception: model.error, - ); - } - }); - return SignInPageContents( - viewModel: signInModel, - title: 'Architecture Demo', - ); - } -} - -class SignInPageContents extends StatelessWidget { - const SignInPageContents( - {Key? key, required this.viewModel, this.title = 'Architecture Demo'}) - : super(key: key); - final SignInViewModel viewModel; - final String title; - - static const Key emailPasswordButtonKey = Key(Keys.emailPassword); - static const Key anonymousButtonKey = Key(Keys.anonymous); - - Future _showEmailPasswordSignInPage(BuildContext context) async { - final navigator = Navigator.of(context); - await navigator.pushNamed( - AppRoutes.emailPasswordSignInPage, - arguments: () => navigator.pop(), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - elevation: 2.0, - title: Text(title), - ), - backgroundColor: Colors.grey[200], - body: _buildSignIn(context), - ); - } - - Widget _buildHeader() { - if (viewModel.isLoading) { - return const Center( - child: CircularProgressIndicator(), - ); - } - return const Text( - Strings.signIn, - textAlign: TextAlign.center, - style: TextStyle(fontSize: 32.0, fontWeight: FontWeight.w600), - ); - } - - Widget _buildSignIn(BuildContext context) { - return Center( - child: LayoutBuilder(builder: (context, constraints) { - return Container( - width: min(constraints.maxWidth, 600), - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox(height: 32.0), - SizedBox( - height: 50.0, - child: _buildHeader(), - ), - const SizedBox(height: 32.0), - SignInButton( - key: emailPasswordButtonKey, - text: Strings.signInWithEmailPassword, - onPressed: viewModel.isLoading - ? null - : () => _showEmailPasswordSignInPage(context), - textColor: Colors.white, - color: Theme.of(context).primaryColor, - ), - const SizedBox(height: 8), - const Text( - Strings.or, - style: TextStyle(fontSize: 14.0, color: Colors.black87), - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - SignInButton( - key: anonymousButtonKey, - text: Strings.goAnonymous, - color: Theme.of(context).primaryColor, - textColor: Colors.white, - onPressed: - viewModel.isLoading ? null : viewModel.signInAnonymously, - ), - ], - ), - ); - }), - ); - } -} diff --git a/lib/app/sign_in/sign_in_view_model.dart b/lib/app/sign_in/sign_in_view_model.dart deleted file mode 100644 index b340e72d..00000000 --- a/lib/app/sign_in/sign_in_view_model.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'dart:async'; - -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/foundation.dart'; - -class SignInViewModel with ChangeNotifier { - SignInViewModel({required this.auth}); - final FirebaseAuth auth; - bool isLoading = false; - dynamic error; - - Future _signIn(Future Function() signInMethod) async { - try { - isLoading = true; - notifyListeners(); - await signInMethod(); - error = null; - } catch (e) { - error = e; - rethrow; - } finally { - isLoading = false; - notifyListeners(); - } - } - - Future signInAnonymously() async { - await _signIn(auth.signInAnonymously); - } -} diff --git a/lib/app/top_level_providers.dart b/lib/app/top_level_providers.dart deleted file mode 100644 index b908e819..00000000 --- a/lib/app/top_level_providers.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:logger/logger.dart'; -import 'package:starter_architecture_flutter_firebase/services/firestore_database.dart'; - -final firebaseAuthProvider = - Provider((ref) => FirebaseAuth.instance); - -final authStateChangesProvider = StreamProvider.autoDispose( - (ref) => ref.watch(firebaseAuthProvider).authStateChanges()); - -final databaseProvider = Provider.autoDispose((ref) { - final auth = ref.watch(authStateChangesProvider); - - if (auth.asData?.value?.uid != null) { - return FirestoreDatabase(uid: auth.asData!.value!.uid); - } - return null; -}); - -final loggerProvider = Provider((ref) => Logger( - printer: PrettyPrinter( - methodCount: 1, - printEmojis: false, - ), - )); diff --git a/lib/generated_plugin_registrant.dart b/lib/generated_plugin_registrant.dart deleted file mode 100644 index dac9ba6e..00000000 --- a/lib/generated_plugin_registrant.dart +++ /dev/null @@ -1,22 +0,0 @@ -// -// Generated file. Do not edit. -// - -// ignore_for_file: directives_ordering -// ignore_for_file: lines_longer_than_80_chars - -import 'package:cloud_firestore_web/cloud_firestore_web.dart'; -import 'package:firebase_auth_web/firebase_auth_web.dart'; -import 'package:firebase_core_web/firebase_core_web.dart'; -import 'package:shared_preferences_web/shared_preferences_web.dart'; - -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; - -// ignore: public_member_api_docs -void registerPlugins(Registrar registrar) { - FirebaseFirestoreWeb.registerWith(registrar); - FirebaseAuthWeb.registerWith(registrar); - FirebaseCoreWeb.registerWith(registrar); - SharedPreferencesPlugin.registerWith(registrar); - registrar.registerMessageHandler(); -} diff --git a/lib/main.dart b/lib/main.dart index 70dfddc1..0098de20 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,50 +1,61 @@ //import 'package:auth_widget_builder/auth_widget_builder.dart'; import 'package:firebase_core/firebase_core.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:starter_architecture_flutter_firebase/app/auth_widget.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/home_page.dart'; -import 'package:starter_architecture_flutter_firebase/app/onboarding/onboarding_page.dart'; -import 'package:starter_architecture_flutter_firebase/app/onboarding/onboarding_view_model.dart'; -import 'package:starter_architecture_flutter_firebase/app/top_level_providers.dart'; -import 'package:starter_architecture_flutter_firebase/app/sign_in/sign_in_page.dart'; -import 'package:starter_architecture_flutter_firebase/routing/app_router.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:starter_architecture_flutter_firebase/services/shared_preferences_service.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:starter_architecture_flutter_firebase/firebase_options.dart'; +import 'package:starter_architecture_flutter_firebase/src/app.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/localization/string_hardcoded.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/onboarding/data/onboarding_repository.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); final sharedPreferences = await SharedPreferences.getInstance(); - runApp(ProviderScope( + // * Register error handlers. For more info, see: + // * https://docs.flutter.dev/testing/errors + registerErrorHandlers(); + // * Entry point of the app + + final container = ProviderContainer( overrides: [ - sharedPreferencesServiceProvider.overrideWithValue( - SharedPreferencesService(sharedPreferences), + onboardingRepositoryProvider.overrideWithValue( + OnboardingRepository(sharedPreferences), ), ], - child: MyApp(), + ); + // await until auth state is determined + // this will prevent unnecessary redirects inside GoRouter when the app starts + await container.read(authStateChangesProvider.future); + runApp(UncontrolledProviderScope( + container: container, + child: const MyApp(), )); } -class MyApp extends ConsumerWidget { - @override - Widget build(BuildContext context, WidgetRef ref) { - final firebaseAuth = ref.watch(firebaseAuthProvider); - return MaterialApp( - theme: ThemeData(primarySwatch: Colors.indigo), - debugShowCheckedModeBanner: false, - home: AuthWidget( - nonSignedInBuilder: (_) => Consumer( - builder: (context, ref, _) { - final didCompleteOnboarding = - ref.watch(onboardingViewModelProvider); - return didCompleteOnboarding ? SignInPage() : OnboardingPage(); - }, - ), - signedInBuilder: (_) => HomePage(), +void registerErrorHandlers() { + // * Show some error UI if any uncaught exception happens + FlutterError.onError = (FlutterErrorDetails details) { + FlutterError.presentError(details); + debugPrint(details.toString()); + }; + // * Handle errors from the underlying platform/OS + PlatformDispatcher.instance.onError = (Object error, StackTrace stack) { + debugPrint(error.toString()); + return true; + }; + // * Show some error UI when any widget in the app fails to build + ErrorWidget.builder = (FlutterErrorDetails details) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.red, + title: Text('An error occurred'.hardcoded), ), - onGenerateRoute: (settings) => - AppRouter.onGenerateRoute(settings, firebaseAuth), + body: Center(child: Text(details.toString())), ); - } + }; } diff --git a/lib/routing/app_router.dart b/lib/routing/app_router.dart deleted file mode 100644 index 49a821c6..00000000 --- a/lib/routing/app_router.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:email_password_sign_in_ui/email_password_sign_in_ui.dart'; -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/material.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/job_entries/entry_page.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/jobs/edit_job_page.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/entry.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/job.dart'; - -class AppRoutes { - static const emailPasswordSignInPage = '/email-password-sign-in-page'; - static const editJobPage = '/edit-job-page'; - static const entryPage = '/entry-page'; -} - -class AppRouter { - static Route? onGenerateRoute( - RouteSettings settings, FirebaseAuth firebaseAuth) { - final args = settings.arguments; - switch (settings.name) { - case AppRoutes.emailPasswordSignInPage: - return MaterialPageRoute( - builder: (_) => EmailPasswordSignInPage.withFirebaseAuth(firebaseAuth, - onSignedIn: args as void Function()), - settings: settings, - fullscreenDialog: true, - ); - case AppRoutes.editJobPage: - return MaterialPageRoute( - builder: (_) => EditJobPage(job: args as Job?), - settings: settings, - fullscreenDialog: true, - ); - case AppRoutes.entryPage: - final mapArgs = args as Map; - final job = mapArgs['job'] as Job; - final entry = mapArgs['entry'] as Entry?; - return MaterialPageRoute( - builder: (_) => EntryPage(job: job, entry: entry), - settings: settings, - fullscreenDialog: true, - ); - default: - // TODO: Throw - return null; - } - } -} diff --git a/lib/routing/cupertino_tab_view_router.dart b/lib/routing/cupertino_tab_view_router.dart deleted file mode 100644 index c87fc644..00000000 --- a/lib/routing/cupertino_tab_view_router.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/job_entries/job_entries_page.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/job.dart'; - -class CupertinoTabViewRoutes { - static const jobEntriesPage = '/job-entries-page'; -} - -class CupertinoTabViewRouter { - static Route? generateRoute(RouteSettings settings) { - switch (settings.name) { - case CupertinoTabViewRoutes.jobEntriesPage: - final job = settings.arguments as Job; - return CupertinoPageRoute( - builder: (_) => JobEntriesPage(job: job), - settings: settings, - fullscreenDialog: false, - ); - } - return null; - } -} diff --git a/lib/services/firestore_database.dart b/lib/services/firestore_database.dart deleted file mode 100644 index 487ed267..00000000 --- a/lib/services/firestore_database.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'dart:async'; - -import 'package:firestore_service/firestore_service.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/entry.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/job.dart'; -import 'package:starter_architecture_flutter_firebase/services/firestore_path.dart'; - -String documentIdFromCurrentDate() => DateTime.now().toIso8601String(); - -class FirestoreDatabase { - FirestoreDatabase({required this.uid}); - final String uid; - - final _service = FirestoreService.instance; - - Future setJob(Job job) => _service.setData( - path: FirestorePath.job(uid, job.id), - data: job.toMap(), - ); - - Future deleteJob(Job job) async { - // delete where entry.jobId == job.jobId - final allEntries = await entriesStream(job: job).first; - for (final entry in allEntries) { - if (entry.jobId == job.id) { - await deleteEntry(entry); - } - } - // delete job - await _service.deleteData(path: FirestorePath.job(uid, job.id)); - } - - Stream jobStream({required String jobId}) => _service.documentStream( - path: FirestorePath.job(uid, jobId), - builder: (data, documentId) => Job.fromMap(data, documentId), - ); - - Stream> jobsStream() => _service.collectionStream( - path: FirestorePath.jobs(uid), - builder: (data, documentId) => Job.fromMap(data, documentId), - ); - - Future setEntry(Entry entry) => _service.setData( - path: FirestorePath.entry(uid, entry.id), - data: entry.toMap(), - ); - - Future deleteEntry(Entry entry) => - _service.deleteData(path: FirestorePath.entry(uid, entry.id)); - - Stream> entriesStream({Job? job}) => - _service.collectionStream( - path: FirestorePath.entries(uid), - queryBuilder: job != null - ? (query) => query.where('jobId', isEqualTo: job.id) - : null, - builder: (data, documentID) => Entry.fromMap(data, documentID), - sort: (lhs, rhs) => rhs.start.compareTo(lhs.start), - ); -} diff --git a/lib/services/firestore_path.dart b/lib/services/firestore_path.dart deleted file mode 100644 index 367a28ea..00000000 --- a/lib/services/firestore_path.dart +++ /dev/null @@ -1,7 +0,0 @@ -class FirestorePath { - static String job(String uid, String jobId) => 'users/$uid/jobs/$jobId'; - static String jobs(String uid) => 'users/$uid/jobs'; - static String entry(String uid, String entryId) => - 'users/$uid/entries/$entryId'; - static String entries(String uid) => 'users/$uid/entries'; -} diff --git a/lib/src/app.dart b/lib/src/app.dart new file mode 100644 index 00000000..4103c8a4 --- /dev/null +++ b/lib/src/app.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/routing/app_router.dart'; + +class MyApp extends ConsumerWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final goRouter = ref.watch(goRouterProvider); + return MaterialApp.router( + routerConfig: goRouter, + theme: ThemeData( + primarySwatch: Colors.indigo, + unselectedWidgetColor: Colors.grey, + appBarTheme: const AppBarTheme( + elevation: 2.0, + centerTitle: true, + ), + scaffoldBackgroundColor: Colors.grey[200], + ), + debugShowCheckedModeBanner: false, + ); + } +} diff --git a/lib/src/common_widgets/action_text_button.dart b/lib/src/common_widgets/action_text_button.dart new file mode 100644 index 00000000..812eba54 --- /dev/null +++ b/lib/src/common_widgets/action_text_button.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:starter_architecture_flutter_firebase/src/constants/app_sizes.dart'; + +/// Text button to be used as an [AppBar] action +class ActionTextButton extends StatelessWidget { + const ActionTextButton({super.key, required this.text, this.onPressed}); + final String text; + final VoidCallback? onPressed; + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: Sizes.p16), + child: TextButton( + onPressed: onPressed, + child: Text(text, + style: Theme.of(context) + .textTheme + .headline6! + .copyWith(color: Colors.white)), + ), + ); + } +} diff --git a/lib/src/common_widgets/async_value_widget.dart b/lib/src/common_widgets/async_value_widget.dart new file mode 100644 index 00000000..33137475 --- /dev/null +++ b/lib/src/common_widgets/async_value_widget.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/error_message_widget.dart'; + +class AsyncValueWidget extends StatelessWidget { + const AsyncValueWidget({super.key, required this.value, required this.data}); + final AsyncValue value; + final Widget Function(T) data; + + @override + Widget build(BuildContext context) { + return value.when( + data: data, + error: (e, st) => Center(child: ErrorMessageWidget(e.toString())), + loading: () => const Center(child: CircularProgressIndicator()), + ); + } +} + +class ScaffoldAsyncValueWidget extends StatelessWidget { + const ScaffoldAsyncValueWidget( + {super.key, required this.value, required this.data}); + final AsyncValue value; + final Widget Function(T) data; + + @override + Widget build(BuildContext context) { + return value.when( + data: data, + error: (e, st) => Scaffold( + appBar: AppBar(), + body: Center(child: ErrorMessageWidget(e.toString())), + ), + loading: () => Scaffold( + appBar: AppBar(), + body: const Center(child: CircularProgressIndicator()), + ), + ); + } +} diff --git a/lib/common_widgets/avatar.dart b/lib/src/common_widgets/avatar.dart similarity index 95% rename from lib/common_widgets/avatar.dart rename to lib/src/common_widgets/avatar.dart index 02ad230e..d4f1d169 100644 --- a/lib/common_widgets/avatar.dart +++ b/lib/src/common_widgets/avatar.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; class Avatar extends StatelessWidget { const Avatar({ + super.key, this.photoUrl, required this.radius, this.borderColor, diff --git a/lib/src/common_widgets/custom_text_button.dart b/lib/src/common_widgets/custom_text_button.dart new file mode 100644 index 00000000..f710ba3f --- /dev/null +++ b/lib/src/common_widgets/custom_text_button.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:starter_architecture_flutter_firebase/src/constants/app_sizes.dart'; + +/// Custom text button with a fixed height +class CustomTextButton extends StatelessWidget { + const CustomTextButton( + {super.key, required this.text, this.style, this.onPressed}); + final String text; + final TextStyle? style; + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: Sizes.p48, + child: TextButton( + onPressed: onPressed, + child: Text( + text, + style: style, + textAlign: TextAlign.center, + ), + ), + ); + } +} diff --git a/lib/common_widgets/date_time_picker.dart b/lib/src/common_widgets/date_time_picker.dart similarity index 91% rename from lib/common_widgets/date_time_picker.dart rename to lib/src/common_widgets/date_time_picker.dart index 5cfd166f..5856edda 100644 --- a/lib/common_widgets/date_time_picker.dart +++ b/lib/src/common_widgets/date_time_picker.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/job_entries/format.dart'; -import 'package:starter_architecture_flutter_firebase/common_widgets/input_dropdown.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/input_dropdown.dart'; +import 'package:starter_architecture_flutter_firebase/src/utils/format.dart'; class DateTimePicker extends StatelessWidget { const DateTimePicker({ diff --git a/lib/app/home/jobs/empty_content.dart b/lib/src/common_widgets/empty_content.dart similarity index 100% rename from lib/app/home/jobs/empty_content.dart rename to lib/src/common_widgets/empty_content.dart diff --git a/lib/src/common_widgets/error_message_widget.dart b/lib/src/common_widgets/error_message_widget.dart new file mode 100644 index 00000000..e2e86cba --- /dev/null +++ b/lib/src/common_widgets/error_message_widget.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class ErrorMessageWidget extends StatelessWidget { + const ErrorMessageWidget(this.errorMessage, {super.key}); + final String errorMessage; + @override + Widget build(BuildContext context) { + return Text( + errorMessage, + style: Theme.of(context).textTheme.headline6!.copyWith(color: Colors.red), + ); + } +} diff --git a/lib/common_widgets/input_dropdown.dart b/lib/src/common_widgets/input_dropdown.dart similarity index 100% rename from lib/common_widgets/input_dropdown.dart rename to lib/src/common_widgets/input_dropdown.dart diff --git a/lib/app/home/jobs/list_items_builder.dart b/lib/src/common_widgets/list_items_builder.dart similarity index 54% rename from lib/app/home/jobs/list_items_builder.dart rename to lib/src/common_widgets/list_items_builder.dart index f7182d2b..d4866ca0 100644 --- a/lib/app/home/jobs/list_items_builder.dart +++ b/lib/src/common_widgets/list_items_builder.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/jobs/empty_content.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/empty_content.dart'; typedef ItemWidgetBuilder = Widget Function(BuildContext context, T item); @@ -16,8 +16,18 @@ class ListItemsBuilder extends StatelessWidget { @override Widget build(BuildContext context) { return data.when( - data: (items) => - items.isNotEmpty ? _buildList(items) : const EmptyContent(), + data: (items) => items.isNotEmpty + ? ListView.separated( + itemCount: items.length + 2, + separatorBuilder: (context, index) => const Divider(height: 0.5), + itemBuilder: (context, index) { + if (index == 0 || index == items.length + 1) { + return const SizedBox.shrink(); + } + return itemBuilder(context, items[index - 1]); + }, + ) + : const EmptyContent(), loading: () => const Center(child: CircularProgressIndicator()), error: (_, __) => const EmptyContent( title: 'Something went wrong', @@ -25,17 +35,4 @@ class ListItemsBuilder extends StatelessWidget { ), ); } - - Widget _buildList(List items) { - return ListView.separated( - itemCount: items.length + 2, - separatorBuilder: (context, index) => const Divider(height: 0.5), - itemBuilder: (context, index) { - if (index == 0 || index == items.length + 1) { - return Container(); // zero height: not visible - } - return itemBuilder(context, items[index - 1]); - }, - ); - } } diff --git a/lib/src/common_widgets/primary_button.dart b/lib/src/common_widgets/primary_button.dart new file mode 100644 index 00000000..7e037778 --- /dev/null +++ b/lib/src/common_widgets/primary_button.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:starter_architecture_flutter_firebase/src/constants/app_sizes.dart'; + +/// Primary button based on [ElevatedButton]. +/// Useful for CTAs in the app. +/// @param text - text to display on the button. +/// @param isLoading - if true, a loading indicator will be displayed instead of +/// the text. +/// @param onPressed - callback to be called when the button is pressed. +class PrimaryButton extends StatelessWidget { + const PrimaryButton( + {super.key, required this.text, this.isLoading = false, this.onPressed}); + final String text; + final bool isLoading; + final VoidCallback? onPressed; + @override + Widget build(BuildContext context) { + return SizedBox( + height: Sizes.p48, + child: ElevatedButton( + onPressed: onPressed, + child: isLoading + ? const CircularProgressIndicator() + : Text( + text, + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .headline6! + .copyWith(color: Colors.white), + ), + ), + ); + } +} diff --git a/lib/src/common_widgets/responsive_center.dart b/lib/src/common_widgets/responsive_center.dart new file mode 100644 index 00000000..95d4d5d4 --- /dev/null +++ b/lib/src/common_widgets/responsive_center.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:starter_architecture_flutter_firebase/src/constants/breakpoints.dart'; + +/// Reusable widget for showing a child with a maximum content width constraint. +/// If available width is larger than the maximum width, the child will be +/// centered. +/// If available width is smaller than the maximum width, the child use all the +/// available width. +class ResponsiveCenter extends StatelessWidget { + const ResponsiveCenter({ + super.key, + this.maxContentWidth = Breakpoint.desktop, + this.padding = EdgeInsets.zero, + required this.child, + }); + final double maxContentWidth; + final EdgeInsetsGeometry padding; + final Widget child; + + @override + Widget build(BuildContext context) { + // Use Center as it has *unconstrained* width (loose constraints) + return Center( + // together with SizedBox to specify the max width (tight constraints) + // See this thread for more info: + // https://twitter.com/biz84/status/1445400059894542337 + child: SizedBox( + width: maxContentWidth, + child: Padding( + padding: padding, + child: child, + ), + ), + ); + } +} + +/// Sliver-equivalent of [ResponsiveCenter]. +class ResponsiveSliverCenter extends StatelessWidget { + const ResponsiveSliverCenter({ + super.key, + this.maxContentWidth = Breakpoint.desktop, + this.padding = EdgeInsets.zero, + required this.child, + }); + final double maxContentWidth; + final EdgeInsetsGeometry padding; + final Widget child; + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: ResponsiveCenter( + maxContentWidth: maxContentWidth, + padding: padding, + child: child, + ), + ); + } +} diff --git a/lib/src/common_widgets/responsive_scrollable_card.dart b/lib/src/common_widgets/responsive_scrollable_card.dart new file mode 100644 index 00000000..cf2e90fa --- /dev/null +++ b/lib/src/common_widgets/responsive_scrollable_card.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/responsive_center.dart'; +import 'package:starter_architecture_flutter_firebase/src/constants/app_sizes.dart'; +import 'package:starter_architecture_flutter_firebase/src/constants/breakpoints.dart'; + +/// Scrollable widget that shows a responsive card with a given child widget. +/// Useful for displaying forms and other widgets that need to be scrollable. +class ResponsiveScrollableCard extends StatelessWidget { + const ResponsiveScrollableCard({super.key, required this.child}); + final Widget child; + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: ResponsiveCenter( + maxContentWidth: Breakpoint.tablet, + child: Padding( + padding: const EdgeInsets.all(Sizes.p16), + child: Card( + child: Padding( + padding: const EdgeInsets.all(Sizes.p16), + child: child, + ), + ), + ), + ), + ); + } +} diff --git a/lib/common_widgets/segmented_control.dart b/lib/src/common_widgets/segmented_control.dart similarity index 98% rename from lib/common_widgets/segmented_control.dart rename to lib/src/common_widgets/segmented_control.dart index 4422906e..c3486343 100644 --- a/lib/common_widgets/segmented_control.dart +++ b/lib/src/common_widgets/segmented_control.dart @@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart'; class SegmentedControl extends StatelessWidget { const SegmentedControl({ + super.key, required this.header, required this.value, required this.children, diff --git a/lib/src/constants/app_sizes.dart b/lib/src/constants/app_sizes.dart new file mode 100644 index 00000000..cf240a26 --- /dev/null +++ b/lib/src/constants/app_sizes.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; + +/// Constant sizes to be used in the app (paddings, gaps, rounded corners etc.) +class Sizes { + static const p4 = 4.0; + static const p8 = 8.0; + static const p12 = 12.0; + static const p16 = 16.0; + static const p20 = 20.0; + static const p24 = 24.0; + static const p32 = 32.0; + static const p48 = 48.0; + static const p64 = 64.0; +} + +/// Constant gap widths +const gapW4 = SizedBox(width: Sizes.p4); +const gapW8 = SizedBox(width: Sizes.p8); +const gapW12 = SizedBox(width: Sizes.p12); +const gapW16 = SizedBox(width: Sizes.p16); +const gapW20 = SizedBox(width: Sizes.p20); +const gapW24 = SizedBox(width: Sizes.p24); +const gapW32 = SizedBox(width: Sizes.p32); +const gapW48 = SizedBox(width: Sizes.p48); +const gapW64 = SizedBox(width: Sizes.p64); + +/// Constant gap heights +const gapH4 = SizedBox(height: Sizes.p4); +const gapH8 = SizedBox(height: Sizes.p8); +const gapH12 = SizedBox(height: Sizes.p12); +const gapH16 = SizedBox(height: Sizes.p16); +const gapH20 = SizedBox(height: Sizes.p20); +const gapH24 = SizedBox(height: Sizes.p24); +const gapH32 = SizedBox(height: Sizes.p32); +const gapH48 = SizedBox(height: Sizes.p48); +const gapH64 = SizedBox(height: Sizes.p64); diff --git a/lib/src/constants/breakpoints.dart b/lib/src/constants/breakpoints.dart new file mode 100644 index 00000000..34ee032c --- /dev/null +++ b/lib/src/constants/breakpoints.dart @@ -0,0 +1,5 @@ +/// Layout breakpoints used in the app. +class Breakpoint { + static const double desktop = 900; + static const double tablet = 600; +} diff --git a/lib/constants/keys.dart b/lib/src/constants/keys.dart similarity index 100% rename from lib/constants/keys.dart rename to lib/src/constants/keys.dart diff --git a/lib/constants/strings.dart b/lib/src/constants/strings.dart similarity index 90% rename from lib/constants/strings.dart rename to lib/src/constants/strings.dart index 04a83564..ef092248 100644 --- a/lib/constants/strings.dart +++ b/lib/src/constants/strings.dart @@ -11,8 +11,7 @@ class Strings { // Sign In Page static const String signIn = 'Sign in'; - static const String signInWithEmailPassword = - 'Sign in with email and password'; + static const String signInWithEmailPassword = 'Sign in with email & password'; static const String goAnonymous = 'Go anonymous'; static const String or = 'or'; static const String signInFailed = 'Sign in failed'; diff --git a/lib/src/features/authentication/data/firebase_auth_repository.dart b/lib/src/features/authentication/data/firebase_auth_repository.dart new file mode 100644 index 00000000..4ea94cc8 --- /dev/null +++ b/lib/src/features/authentication/data/firebase_auth_repository.dart @@ -0,0 +1,38 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class AuthRepository { + AuthRepository(this._auth); + final FirebaseAuth _auth; + + Stream authStateChanges() => _auth.authStateChanges(); + User? get currentUser => _auth.currentUser; + + Future signInAnonymously() { + return _auth.signInAnonymously(); + } + + Future signInWithEmailAndPassword(String email, String password) { + return _auth.signInWithEmailAndPassword(email: email, password: password); + } + + Future createUserWithEmailAndPassword(String email, String password) { + return _auth.createUserWithEmailAndPassword( + email: email, password: password); + } + + Future signOut() { + return _auth.signOut(); + } +} + +final firebaseAuthProvider = + Provider((ref) => FirebaseAuth.instance); + +final authRepositoryProvider = Provider((ref) { + return AuthRepository(ref.watch(firebaseAuthProvider)); +}); + +final authStateChangesProvider = StreamProvider((ref) { + return ref.watch(authRepositoryProvider).authStateChanges(); +}); diff --git a/lib/src/features/authentication/domain/app_user.dart b/lib/src/features/authentication/domain/app_user.dart new file mode 100644 index 00000000..87d1d59e --- /dev/null +++ b/lib/src/features/authentication/domain/app_user.dart @@ -0,0 +1,26 @@ +/// Type defining a user ID from Firebase. +typedef UserID = String; + +// TODO: FirebaseAppUser? +/// Simple class representing the user UID and email. +class AppUser { + const AppUser({ + required this.uid, + required this.email, + }); + final String uid; + final String email; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is AppUser && other.uid == uid && other.email == email; + } + + @override + int get hashCode => uid.hashCode ^ email.hashCode; + + @override + String toString() => 'AppUser(uid: $uid, email: $email)'; +} diff --git a/lib/src/features/authentication/domain/fake_app_user.dart b/lib/src/features/authentication/domain/fake_app_user.dart new file mode 100644 index 00000000..fc35a6ef --- /dev/null +++ b/lib/src/features/authentication/domain/fake_app_user.dart @@ -0,0 +1,11 @@ +import 'package:starter_architecture_flutter_firebase/src/features/authentication/domain/app_user.dart'; + +/// Fake user class used to simulate a user account on the backend +class FakeAppUser extends AppUser { + FakeAppUser({ + required super.uid, + required super.email, + required this.password, + }); + final String password; +} diff --git a/lib/src/features/authentication/presentation/account/account_screen.dart b/lib/src/features/authentication/presentation/account/account_screen.dart new file mode 100644 index 00000000..25b31c0a --- /dev/null +++ b/lib/src/features/authentication/presentation/account/account_screen.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/action_text_button.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/avatar.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/account/account_screen_controller.dart'; +import 'package:starter_architecture_flutter_firebase/src/localization/string_hardcoded.dart'; +import 'package:starter_architecture_flutter_firebase/src/utils/alert_dialogs.dart'; +import 'package:starter_architecture_flutter_firebase/src/utils/async_value_ui.dart'; + +class AccountScreen extends ConsumerWidget { + const AccountScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen( + accountScreenControllerProvider, + (_, state) => state.showAlertDialogOnError(context), + ); + final state = ref.watch(accountScreenControllerProvider); + final user = ref.watch(authRepositoryProvider).currentUser; + return Scaffold( + appBar: AppBar( + title: state.isLoading + ? const CircularProgressIndicator() + : Text('Account'.hardcoded), + actions: [ + ActionTextButton( + text: 'Logout'.hardcoded, + onPressed: state.isLoading + ? null + : () async { + final logout = await showAlertDialog( + context: context, + title: 'Are you sure?'.hardcoded, + cancelActionText: 'Cancel'.hardcoded, + defaultActionText: 'Logout'.hardcoded, + ); + if (logout == true) { + ref + .read(accountScreenControllerProvider.notifier) + .signOut(); + } + }, + ), + ], + bottom: PreferredSize( + preferredSize: const Size.fromHeight(130.0), + child: Column( + children: [ + if (user != null) ...[ + Avatar( + photoUrl: user.photoURL, + radius: 50, + borderColor: Colors.black54, + borderWidth: 2.0, + ), + const SizedBox(height: 8), + if (user.displayName != null) + Text( + user.displayName!, + style: const TextStyle(color: Colors.white), + ), + const SizedBox(height: 8), + ], + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/features/authentication/presentation/account/account_screen_controller.dart b/lib/src/features/authentication/presentation/account/account_screen_controller.dart new file mode 100644 index 00000000..18fc8e8a --- /dev/null +++ b/lib/src/features/authentication/presentation/account/account_screen_controller.dart @@ -0,0 +1,21 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; + +class AccountScreenController extends AutoDisposeAsyncNotifier { + @override + FutureOr build() { + // ok to leave this empty if the return type is FutureOr + } + + Future signOut() async { + final authRepository = ref.read(authRepositoryProvider); + state = const AsyncLoading(); + state = await AsyncValue.guard(authRepository.signOut); + } +} + +final accountScreenControllerProvider = + AutoDisposeAsyncNotifierProvider( + AccountScreenController.new); diff --git a/lib/src/features/authentication/presentation/email_password/email_password_sign_in_controller.dart b/lib/src/features/authentication/presentation/email_password/email_password_sign_in_controller.dart new file mode 100644 index 00000000..f8f57fe2 --- /dev/null +++ b/lib/src/features/authentication/presentation/email_password/email_password_sign_in_controller.dart @@ -0,0 +1,36 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/email_password/email_password_sign_in_form_type.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; + +class EmailPasswordSignInController extends AutoDisposeAsyncNotifier { + @override + FutureOr build() { + // ok to leave this empty if the return type is FutureOr + } + + Future submit( + {required String email, + required String password, + required EmailPasswordSignInFormType formType}) async { + state = const AsyncValue.loading(); + state = + await AsyncValue.guard(() => _authenticate(email, password, formType)); + } + + Future _authenticate( + String email, String password, EmailPasswordSignInFormType formType) { + final authRepository = ref.read(authRepositoryProvider); + switch (formType) { + case EmailPasswordSignInFormType.signIn: + return authRepository.signInWithEmailAndPassword(email, password); + case EmailPasswordSignInFormType.register: + return authRepository.createUserWithEmailAndPassword(email, password); + } + } +} + +final emailPasswordSignInControllerProvider = + AutoDisposeAsyncNotifierProvider( + EmailPasswordSignInController.new); diff --git a/lib/src/features/authentication/presentation/email_password/email_password_sign_in_form_type.dart b/lib/src/features/authentication/presentation/email_password/email_password_sign_in_form_type.dart new file mode 100644 index 00000000..8a112d90 --- /dev/null +++ b/lib/src/features/authentication/presentation/email_password/email_password_sign_in_form_type.dart @@ -0,0 +1,55 @@ +import 'package:starter_architecture_flutter_firebase/src/localization/string_hardcoded.dart'; + +/// Form type for email & password authentication +enum EmailPasswordSignInFormType { signIn, register } + +extension EmailPasswordSignInFormTypeX on EmailPasswordSignInFormType { + String get passwordLabelText { + if (this == EmailPasswordSignInFormType.register) { + return 'Password (8+ characters)'.hardcoded; + } else { + return 'Password'.hardcoded; + } + } + + // Getters + String get primaryButtonText { + if (this == EmailPasswordSignInFormType.register) { + return 'Create an account'.hardcoded; + } else { + return 'Sign in'.hardcoded; + } + } + + String get secondaryButtonText { + if (this == EmailPasswordSignInFormType.register) { + return 'Have an account? Sign in'.hardcoded; + } else { + return 'Need an account? Register'.hardcoded; + } + } + + EmailPasswordSignInFormType get secondaryActionFormType { + if (this == EmailPasswordSignInFormType.register) { + return EmailPasswordSignInFormType.signIn; + } else { + return EmailPasswordSignInFormType.register; + } + } + + String get errorAlertTitle { + if (this == EmailPasswordSignInFormType.register) { + return 'Registration failed'.hardcoded; + } else { + return 'Sign in failed'.hardcoded; + } + } + + String get title { + if (this == EmailPasswordSignInFormType.register) { + return 'Register'.hardcoded; + } else { + return 'Sign in'.hardcoded; + } + } +} diff --git a/lib/src/features/authentication/presentation/email_password/email_password_sign_in_screen.dart b/lib/src/features/authentication/presentation/email_password/email_password_sign_in_screen.dart new file mode 100644 index 00000000..5a27488b --- /dev/null +++ b/lib/src/features/authentication/presentation/email_password/email_password_sign_in_screen.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/custom_text_button.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/primary_button.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/responsive_scrollable_card.dart'; +import 'package:starter_architecture_flutter_firebase/src/constants/app_sizes.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/email_password/email_password_sign_in_controller.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/email_password/email_password_sign_in_form_type.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/email_password/email_password_sign_in_validators.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/email_password/string_validators.dart'; +import 'package:starter_architecture_flutter_firebase/src/localization/string_hardcoded.dart'; +import 'package:starter_architecture_flutter_firebase/src/utils/async_value_ui.dart'; + +/// Email & password sign in screen. +/// Wraps the [EmailPasswordSignInContents] widget below with a [Scaffold] and +/// [AppBar] with a title. +class EmailPasswordSignInScreen extends StatelessWidget { + const EmailPasswordSignInScreen({super.key, required this.formType}); + final EmailPasswordSignInFormType formType; + + // * Keys for testing using find.byKey() + static const emailKey = Key('email'); + static const passwordKey = Key('password'); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Sign In'.hardcoded)), + body: EmailPasswordSignInContents( + formType: formType, + ), + ); + } +} + +/// A widget for email & password authentication, supporting the following: +/// - sign in +/// - register (create an account) +class EmailPasswordSignInContents extends ConsumerStatefulWidget { + const EmailPasswordSignInContents({ + super.key, + required this.formType, + }); + + /// The default form type to use. + final EmailPasswordSignInFormType formType; + @override + ConsumerState createState() => + _EmailPasswordSignInContentsState(); +} + +class _EmailPasswordSignInContentsState + extends ConsumerState + with EmailAndPasswordValidators { + final _formKey = GlobalKey(); + final _node = FocusScopeNode(); + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + + String get email => _emailController.text; + String get password => _passwordController.text; + + // local variable used to apply AutovalidateMode.onUserInteraction and show + // error hints only when the form has been submitted + // For more details on how this is implemented, see: + // https://codewithandrea.com/articles/flutter-text-field-form-validation/ + var _submitted = false; + // track the formType as a local state variable + late var _formType = widget.formType; + + @override + void dispose() { + // * TextEditingControllers should be always disposed + _node.dispose(); + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + Future _submit() async { + setState(() => _submitted = true); + // only submit the form if validation passes + if (_formKey.currentState!.validate()) { + final controller = + ref.read(emailPasswordSignInControllerProvider.notifier); + await controller.submit( + email: email, + password: password, + formType: _formType, + ); + } + } + + void _emailEditingComplete() { + if (canSubmitEmail(email)) { + _node.nextFocus(); + } + } + + void _passwordEditingComplete() { + if (!canSubmitEmail(email)) { + _node.previousFocus(); + return; + } + _submit(); + } + + void _updateFormType() { + // * Toggle between register and sign in form + setState(() => _formType = _formType.secondaryActionFormType); + // * Clear the password field when doing so + _passwordController.clear(); + } + + @override + Widget build(BuildContext context) { + ref.listen( + emailPasswordSignInControllerProvider, + (_, state) => state.showAlertDialogOnError(context), + ); + final state = ref.watch(emailPasswordSignInControllerProvider); + return ResponsiveScrollableCard( + child: FocusScope( + node: _node, + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + gapH8, + // Email field + TextFormField( + key: EmailPasswordSignInScreen.emailKey, + controller: _emailController, + decoration: InputDecoration( + labelText: 'Email'.hardcoded, + hintText: 'test@test.com'.hardcoded, + enabled: !state.isLoading, + ), + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (email) => + !_submitted ? null : emailErrorText(email ?? ''), + autocorrect: false, + textInputAction: TextInputAction.next, + keyboardType: TextInputType.emailAddress, + keyboardAppearance: Brightness.light, + onEditingComplete: () => _emailEditingComplete(), + inputFormatters: [ + ValidatorInputFormatter( + editingValidator: EmailEditingRegexValidator()), + ], + ), + gapH8, + // Password field + TextFormField( + key: EmailPasswordSignInScreen.passwordKey, + controller: _passwordController, + decoration: InputDecoration( + labelText: _formType.passwordLabelText, + enabled: !state.isLoading, + ), + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (password) => !_submitted + ? null + : passwordErrorText(password ?? '', _formType), + obscureText: true, + autocorrect: false, + textInputAction: TextInputAction.done, + keyboardAppearance: Brightness.light, + onEditingComplete: () => _passwordEditingComplete(), + ), + gapH8, + PrimaryButton( + text: _formType.primaryButtonText, + isLoading: state.isLoading, + onPressed: state.isLoading ? null : () => _submit(), + ), + gapH8, + CustomTextButton( + text: _formType.secondaryButtonText, + onPressed: state.isLoading ? null : _updateFormType, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/features/authentication/presentation/email_password/email_password_sign_in_validators.dart b/lib/src/features/authentication/presentation/email_password/email_password_sign_in_validators.dart new file mode 100644 index 00000000..82f82291 --- /dev/null +++ b/lib/src/features/authentication/presentation/email_password/email_password_sign_in_validators.dart @@ -0,0 +1,41 @@ +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/email_password/email_password_sign_in_form_type.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/email_password/string_validators.dart'; +import 'package:starter_architecture_flutter_firebase/src/localization/string_hardcoded.dart'; + +/// Mixin class to be used for client-side email & password validation +mixin EmailAndPasswordValidators { + final StringValidator emailSubmitValidator = EmailSubmitRegexValidator(); + final StringValidator passwordRegisterSubmitValidator = + MinLengthStringValidator(8); + final StringValidator passwordSignInSubmitValidator = + NonEmptyStringValidator(); + + bool canSubmitEmail(String email) { + return emailSubmitValidator.isValid(email); + } + + bool canSubmitPassword( + String password, EmailPasswordSignInFormType formType) { + if (formType == EmailPasswordSignInFormType.register) { + return passwordRegisterSubmitValidator.isValid(password); + } + return passwordSignInSubmitValidator.isValid(password); + } + + String? emailErrorText(String email) { + final bool showErrorText = !canSubmitEmail(email); + final String errorText = email.isEmpty + ? 'Email can\'t be empty'.hardcoded + : 'Invalid email'.hardcoded; + return showErrorText ? errorText : null; + } + + String? passwordErrorText( + String password, EmailPasswordSignInFormType formType) { + final bool showErrorText = !canSubmitPassword(password, formType); + final String errorText = password.isEmpty + ? 'Password can\'t be empty'.hardcoded + : 'Password is too short'.hardcoded; + return showErrorText ? errorText : null; + } +} diff --git a/lib/src/features/authentication/presentation/email_password/string_validators.dart b/lib/src/features/authentication/presentation/email_password/string_validators.dart new file mode 100644 index 00000000..9733e604 --- /dev/null +++ b/lib/src/features/authentication/presentation/email_password/string_validators.dart @@ -0,0 +1,72 @@ +import 'package:flutter/services.dart'; + +/// This file contains some helper functions used for string validation. + +abstract class StringValidator { + bool isValid(String value); +} + +class RegexValidator implements StringValidator { + RegexValidator({required this.regexSource}); + final String regexSource; + + @override + bool isValid(String value) { + try { + // https://regex101.com/ + final RegExp regex = RegExp(regexSource); + final Iterable matches = regex.allMatches(value); + for (final match in matches) { + if (match.start == 0 && match.end == value.length) { + return true; + } + } + return false; + } catch (e) { + // Invalid regex + assert(false, e.toString()); + return true; + } + } +} + +class ValidatorInputFormatter implements TextInputFormatter { + ValidatorInputFormatter({required this.editingValidator}); + final StringValidator editingValidator; + + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + final bool oldValueValid = editingValidator.isValid(oldValue.text); + final bool newValueValid = editingValidator.isValid(newValue.text); + if (oldValueValid && !newValueValid) { + return oldValue; + } + return newValue; + } +} + +class EmailEditingRegexValidator extends RegexValidator { + EmailEditingRegexValidator() : super(regexSource: '^(|\\S)+\$'); +} + +class EmailSubmitRegexValidator extends RegexValidator { + EmailSubmitRegexValidator() : super(regexSource: '^\\S+@\\S+\\.\\S+\$'); +} + +class NonEmptyStringValidator extends StringValidator { + @override + bool isValid(String value) { + return value.isNotEmpty; + } +} + +class MinLengthStringValidator extends StringValidator { + MinLengthStringValidator(this.minLength); + final int minLength; + + @override + bool isValid(String value) { + return value.length >= minLength; + } +} diff --git a/lib/src/features/authentication/presentation/sign_in/sign_in_screen.dart b/lib/src/features/authentication/presentation/sign_in/sign_in_screen.dart new file mode 100644 index 00000000..260ee8de --- /dev/null +++ b/lib/src/features/authentication/presentation/sign_in/sign_in_screen.dart @@ -0,0 +1,83 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/primary_button.dart'; +import 'package:starter_architecture_flutter_firebase/src/constants/keys.dart'; +import 'package:starter_architecture_flutter_firebase/src/constants/strings.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/sign_in/sign_in_screen_controller.dart'; +import 'package:starter_architecture_flutter_firebase/src/routing/app_router.dart'; +import 'package:starter_architecture_flutter_firebase/src/utils/async_value_ui.dart'; + +class SignInScreen extends ConsumerWidget { + const SignInScreen({super.key}); + + static const Key emailPasswordButtonKey = Key(Keys.emailPassword); + static const Key anonymousButtonKey = Key(Keys.anonymous); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen( + signInScreenControllerProvider, + (_, state) => state.showAlertDialogOnError(context), + ); + final state = ref.watch(signInScreenControllerProvider); + return Scaffold( + appBar: AppBar( + title: const Text('Sign In'), + ), + body: Center( + child: LayoutBuilder(builder: (context, constraints) { + return Container( + width: min(constraints.maxWidth, 600), + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 32.0), + // Sign in text or loading UI + SizedBox( + height: 50.0, + child: state.isLoading + ? const Center(child: CircularProgressIndicator()) + : const Text( + Strings.signIn, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 32.0, fontWeight: FontWeight.w600), + ), + ), + const SizedBox(height: 32.0), + PrimaryButton( + key: emailPasswordButtonKey, + text: Strings.signInWithEmailPassword, + onPressed: state.isLoading + ? null + : () => context.goNamed(AppRoute.emailPassword.name), + ), + const SizedBox(height: 8), + const Text( + Strings.or, + style: TextStyle(fontSize: 14.0, color: Colors.black87), + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + PrimaryButton( + key: anonymousButtonKey, + text: Strings.goAnonymous, + onPressed: state.isLoading + ? null + : () => ref + .read(signInScreenControllerProvider.notifier) + .signInAnonymously(), + ), + ], + ), + ); + }), + ), + ); + } +} diff --git a/lib/src/features/authentication/presentation/sign_in/sign_in_screen_controller.dart b/lib/src/features/authentication/presentation/sign_in/sign_in_screen_controller.dart new file mode 100644 index 00000000..ba13cc01 --- /dev/null +++ b/lib/src/features/authentication/presentation/sign_in/sign_in_screen_controller.dart @@ -0,0 +1,21 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; + +class SignInScreenController extends AutoDisposeAsyncNotifier { + @override + FutureOr build() { + // ok to leave this empty if the return type is FutureOr + } + + Future signInAnonymously() async { + final authRepository = ref.read(authRepositoryProvider); + state = const AsyncLoading(); + state = await AsyncValue.guard(authRepository.signInAnonymously); + } +} + +final signInScreenControllerProvider = + AutoDisposeAsyncNotifierProvider( + SignInScreenController.new); diff --git a/lib/src/features/entries/application/entries_service.dart b/lib/src/features/entries/application/entries_service.dart new file mode 100644 index 00000000..7137199b --- /dev/null +++ b/lib/src/features/entries/application/entries_service.dart @@ -0,0 +1,92 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/domain/app_user.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/entries/domain/daily_jobs_details.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/entries/domain/entries_list_tile_model.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/entries/domain/entry_job.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/data/firestore_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/utils/format.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/entry.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart'; + +// TODO: Clean up this code a bit more +class EntriesService { + EntriesService({required this.database}); + final FirestoreRepository database; + + /// combine List, List into List + Stream> _allEntriesStream(UserID uid) => + CombineLatestStream.combine2( + database.watchEntries(uid: uid), + database.watchJobs(uid: uid), + _entriesJobsCombiner, + ); + + static List _entriesJobsCombiner( + List entries, List jobs) { + return entries.map((entry) { + final job = jobs.firstWhere((job) => job.id == entry.jobId); + return EntryJob(entry, job); + }).toList(); + } + + /// Output stream + Stream> entriesTileModelStream(UserID uid) => + _allEntriesStream(uid).map(_createModels); + + static List _createModels(List allEntries) { + if (allEntries.isEmpty) { + return []; + } + final allDailyJobsDetails = DailyJobsDetails.all(allEntries); + + // total duration across all jobs + final totalDuration = allDailyJobsDetails + .map((dateJobsDuration) => dateJobsDuration.duration) + .reduce((value, element) => value + element); + + // total pay across all jobs + final totalPay = allDailyJobsDetails + .map((dateJobsDuration) => dateJobsDuration.pay) + .reduce((value, element) => value + element); + + return [ + EntriesListTileModel( + leadingText: 'All Entries', + middleText: Format.currency(totalPay), + trailingText: Format.hours(totalDuration), + ), + for (DailyJobsDetails dailyJobsDetails in allDailyJobsDetails) ...[ + EntriesListTileModel( + isHeader: true, + leadingText: Format.date(dailyJobsDetails.date), + middleText: Format.currency(dailyJobsDetails.pay), + trailingText: Format.hours(dailyJobsDetails.duration), + ), + for (JobDetails jobDuration in dailyJobsDetails.jobsDetails) + EntriesListTileModel( + leadingText: jobDuration.name, + middleText: Format.currency(jobDuration.pay), + trailingText: Format.hours(jobDuration.durationInHours), + ), + ] + ]; + } +} + +final entriesServiceProvider = Provider((ref) { + return EntriesService(database: ref.watch(databaseProvider)); +}); + +final entriesTileModelStreamProvider = + StreamProvider.autoDispose>( + (ref) { + final user = ref.watch(authStateChangesProvider).value; + if (user == null) { + throw AssertionError('User can\'t be null when fetching entries'); + } + final entriesService = ref.watch(entriesServiceProvider); + return entriesService.entriesTileModelStream(user.uid); + }, +); diff --git a/lib/app/home/entries/daily_jobs_details.dart b/lib/src/features/entries/domain/daily_jobs_details.dart similarity index 96% rename from lib/app/home/entries/daily_jobs_details.dart rename to lib/src/features/entries/domain/daily_jobs_details.dart index 51fbf223..3cfaa4d5 100644 --- a/lib/app/home/entries/daily_jobs_details.dart +++ b/lib/src/features/entries/domain/daily_jobs_details.dart @@ -1,4 +1,4 @@ -import 'package:starter_architecture_flutter_firebase/app/home/entries/entry_job.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/entries/domain/entry_job.dart'; /// Temporary model class to store the time tracked and pay for a job class JobDetails { diff --git a/lib/src/features/entries/domain/entries_list_tile_model.dart b/lib/src/features/entries/domain/entries_list_tile_model.dart new file mode 100644 index 00000000..179afa27 --- /dev/null +++ b/lib/src/features/entries/domain/entries_list_tile_model.dart @@ -0,0 +1,12 @@ +class EntriesListTileModel { + const EntriesListTileModel({ + required this.leadingText, + required this.trailingText, + this.middleText, + this.isHeader = false, + }); + final String leadingText; + final String trailingText; + final String? middleText; + final bool isHeader; +} diff --git a/lib/src/features/entries/domain/entry_job.dart b/lib/src/features/entries/domain/entry_job.dart new file mode 100644 index 00000000..1f02a96f --- /dev/null +++ b/lib/src/features/entries/domain/entry_job.dart @@ -0,0 +1,9 @@ +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/entry.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart'; + +class EntryJob { + EntryJob(this.entry, this.job); + + final Entry entry; + final Job job; +} diff --git a/lib/src/features/entries/presentation/entries_screen.dart b/lib/src/features/entries/presentation/entries_screen.dart new file mode 100644 index 00000000..73e9571e --- /dev/null +++ b/lib/src/features/entries/presentation/entries_screen.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/constants/strings.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/entries/domain/entries_list_tile_model.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/entries/application/entries_service.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/list_items_builder.dart'; + +class EntriesScreen extends ConsumerWidget { + const EntriesScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar( + title: const Text(Strings.entries), + ), + body: Consumer( + builder: (context, ref, child) { + final entriesTileModelStream = + ref.watch(entriesTileModelStreamProvider); + return ListItemsBuilder( + data: entriesTileModelStream, + itemBuilder: (context, model) => EntriesListTile(model: model), + ); + }, + ), + ); + } +} + +class EntriesListTile extends StatelessWidget { + const EntriesListTile({super.key, required this.model}); + final EntriesListTileModel model; + + @override + Widget build(BuildContext context) { + const fontSize = 16.0; + return Container( + color: model.isHeader ? Colors.indigo[100] : null, + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Row( + children: [ + Text(model.leadingText, style: const TextStyle(fontSize: fontSize)), + Expanded(child: Container()), + if (model.middleText != null) + Text( + model.middleText!, + style: TextStyle(color: Colors.green[700], fontSize: fontSize), + textAlign: TextAlign.right, + ), + SizedBox( + width: 60.0, + child: Text( + model.trailingText, + style: const TextStyle(fontSize: fontSize), + textAlign: TextAlign.right, + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/features/jobs/data/firestore_data_source.dart b/lib/src/features/jobs/data/firestore_data_source.dart new file mode 100644 index 00000000..d6874cd9 --- /dev/null +++ b/lib/src/features/jobs/data/firestore_data_source.dart @@ -0,0 +1,93 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class FirestoreDataSource { + const FirestoreDataSource._(); + + Future setData({ + required String path, + required Map data, + bool merge = false, + }) async { + final reference = FirebaseFirestore.instance.doc(path); + await reference.set(data, SetOptions(merge: merge)); + } + + Future deleteData({required String path}) async { + final reference = FirebaseFirestore.instance.doc(path); + await reference.delete(); + } + + // watch collections and documents as streams + Stream> watchCollection({ + required String path, + required T Function(Map? data, String documentID) builder, + Query>? Function(Query> query)? + queryBuilder, + int Function(T lhs, T rhs)? sort, + }) { + Query> query = + FirebaseFirestore.instance.collection(path); + if (queryBuilder != null) { + query = queryBuilder(query)!; + } + final snapshots = query.snapshots(); + return snapshots.map((snapshot) { + final result = snapshot.docs + .map((snapshot) => builder(snapshot.data(), snapshot.id)) + .where((value) => value != null) + .toList(); + if (sort != null) { + result.sort(sort); + } + return result; + }); + } + + Stream watchDocument({ + required String path, + required T Function(Map? data, String documentID) builder, + }) { + final reference = FirebaseFirestore.instance.doc(path); + final Stream>> snapshots = + reference.snapshots(); + return snapshots.map((snapshot) => builder(snapshot.data(), snapshot.id)); + } + + // fetch collections and documents as futures + Future> fetchCollection({ + required String path, + required T Function(Map? data, String documentID) builder, + Query>? Function(Query> query)? + queryBuilder, + int Function(T lhs, T rhs)? sort, + }) async { + Query> query = + FirebaseFirestore.instance.collection(path); + if (queryBuilder != null) { + query = queryBuilder(query)!; + } + final snapshot = await query.get(); + final result = snapshot.docs + .map((snapshot) => builder(snapshot.data(), snapshot.id)) + .where((value) => value != null) + .toList(); + if (sort != null) { + result.sort(sort); + } + return result; + } + + Future fetchDocument({ + required String path, + required T Function(Map? data, String documentID) builder, + }) async { + final reference = FirebaseFirestore.instance.doc(path); + final snapshot = await reference.get(); + return builder(snapshot.data(), snapshot.id); + } +} + +final firestoreDataSourceProvider = Provider((ref) { + return const FirestoreDataSource._(); +}); diff --git a/lib/src/features/jobs/data/firestore_repository.dart b/lib/src/features/jobs/data/firestore_repository.dart new file mode 100644 index 00000000..9edeff26 --- /dev/null +++ b/lib/src/features/jobs/data/firestore_repository.dart @@ -0,0 +1,114 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/domain/app_user.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/entry.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/data/firestore_data_source.dart'; + +String documentIdFromCurrentDate() { + final iso = DateTime.now().toIso8601String(); + return iso.replaceAll(':', '-').replaceAll('.', '-'); +} + +class FirestorePath { + static String job(String uid, String jobId) => 'users/$uid/jobs/$jobId'; + static String jobs(String uid) => 'users/$uid/jobs'; + static String entry(String uid, String entryId) => + 'users/$uid/entries/$entryId'; + static String entries(String uid) => 'users/$uid/entries'; +} + +class FirestoreRepository { + const FirestoreRepository(this._dataSource); + final FirestoreDataSource _dataSource; + + Future setJob({required UserID uid, required Job job}) => + _dataSource.setData( + path: FirestorePath.job(uid, job.id), + data: job.toMap(), + ); + + Future deleteJob({required UserID uid, required Job job}) async { + // delete where entry.jobId == job.jobId + final allEntries = await watchEntries(uid: uid, job: job).first; + for (final entry in allEntries) { + if (entry.jobId == job.id) { + await deleteEntry(uid: uid, entry: entry); + } + } + // delete job + await _dataSource.deleteData(path: FirestorePath.job(uid, job.id)); + } + + Stream watchJob({required UserID uid, required JobID jobId}) => + _dataSource.watchDocument( + path: FirestorePath.job(uid, jobId), + builder: (data, documentId) => Job.fromMap(data, documentId), + ); + + Stream> watchJobs({required UserID uid}) => + _dataSource.watchCollection( + path: FirestorePath.jobs(uid), + builder: (data, documentId) => Job.fromMap(data, documentId), + ); + + Future> fetchJobs({required UserID uid}) => + _dataSource.fetchCollection( + path: FirestorePath.jobs(uid), + builder: (data, documentId) => Job.fromMap(data, documentId), + ); + + Future setEntry({required UserID uid, required Entry entry}) => + _dataSource.setData( + path: FirestorePath.entry(uid, entry.id), + data: entry.toMap(), + ); + + Future deleteEntry({required UserID uid, required Entry entry}) => + _dataSource.deleteData(path: FirestorePath.entry(uid, entry.id)); + + Stream> watchEntries({required UserID uid, Job? job}) => + _dataSource.watchCollection( + path: FirestorePath.entries(uid), + queryBuilder: job != null + ? (query) => query.where('jobId', isEqualTo: job.id) + : null, + builder: (data, documentID) => Entry.fromMap(data, documentID), + sort: (lhs, rhs) => rhs.start.compareTo(lhs.start), + ); +} + +final databaseProvider = Provider((ref) { + return FirestoreRepository(ref.watch(firestoreDataSourceProvider)); +}); + +final jobsStreamProvider = StreamProvider.autoDispose>((ref) { + final user = ref.watch(authStateChangesProvider).value; + if (user == null) { + throw AssertionError('User can\'t be null'); + } + final database = ref.watch(databaseProvider); + return database.watchJobs(uid: user.uid); +}); + +final jobStreamProvider = + StreamProvider.autoDispose.family((ref, jobId) { + final user = ref.watch(authStateChangesProvider).value; + if (user == null) { + throw AssertionError('User can\'t be null'); + } + final database = ref.watch(databaseProvider); + return database.watchJob(uid: user.uid, jobId: jobId); +}); + +final jobEntriesStreamProvider = + StreamProvider.autoDispose.family, Job>((ref, job) { + final user = ref.watch(authStateChangesProvider).value; + if (user == null) { + throw AssertionError('User can\'t be null when fetching jobs'); + } + final database = ref.watch(databaseProvider); + return database.watchEntries(uid: user.uid, job: job); +}); diff --git a/lib/app/home/models/entry.dart b/lib/src/features/jobs/domain/entry.dart similarity index 96% rename from lib/app/home/models/entry.dart rename to lib/src/features/jobs/domain/entry.dart index 56c326a0..5c883fb3 100644 --- a/lib/app/home/models/entry.dart +++ b/lib/src/features/jobs/domain/entry.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +typedef EntryID = String; + class Entry extends Equatable { const Entry({ required this.id, @@ -9,7 +11,7 @@ class Entry extends Equatable { required this.comment, }); - final String id; + final EntryID id; final String jobId; final DateTime start; final DateTime end; diff --git a/lib/app/home/models/job.dart b/lib/src/features/jobs/domain/job.dart similarity index 91% rename from lib/app/home/models/job.dart rename to lib/src/features/jobs/domain/job.dart index 3f2a0fac..8e5bdc3e 100644 --- a/lib/app/home/models/job.dart +++ b/lib/src/features/jobs/domain/job.dart @@ -1,10 +1,12 @@ import 'package:equatable/equatable.dart'; -import 'package:meta/meta.dart'; +import 'package:flutter/foundation.dart'; + +typedef JobID = String; @immutable class Job extends Equatable { const Job({required this.id, required this.name, required this.ratePerHour}); - final String id; + final JobID id; final String name; final int ratePerHour; diff --git a/lib/app/home/jobs/edit_job_page.dart b/lib/src/features/jobs/presentation/edit_job_screen/edit_job_screen.dart similarity index 53% rename from lib/app/home/jobs/edit_job_page.dart rename to lib/src/features/jobs/presentation/edit_job_screen/edit_job_screen.dart index 4e8e9f8e..55c83055 100644 --- a/lib/app/home/jobs/edit_job_page.dart +++ b/lib/src/features/jobs/presentation/edit_job_screen/edit_job_screen.dart @@ -1,29 +1,22 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/job.dart'; -import 'package:alert_dialogs/alert_dialogs.dart'; -import 'package:starter_architecture_flutter_firebase/app/top_level_providers.dart'; -import 'package:starter_architecture_flutter_firebase/routing/app_router.dart'; -import 'package:starter_architecture_flutter_firebase/services/firestore_database.dart'; -import 'package:pedantic/pedantic.dart'; +import 'package:go_router/go_router.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/presentation/edit_job_screen/edit_job_screen_controller.dart'; +import 'package:starter_architecture_flutter_firebase/src/utils/async_value_ui.dart'; -class EditJobPage extends ConsumerStatefulWidget { - const EditJobPage({Key? key, this.job}) : super(key: key); +class EditJobScreen extends ConsumerStatefulWidget { + const EditJobScreen({super.key, this.jobId, this.job}); + final JobID? jobId; final Job? job; - static Future show(BuildContext context, {Job? job}) async { - await Navigator.of(context, rootNavigator: true).pushNamed( - AppRoutes.editJobPage, - arguments: job, - ); - } - @override - _EditJobPageState createState() => _EditJobPageState(); + ConsumerState createState() => _EditJobPageState(); } -class _EditJobPageState extends ConsumerState { +class _EditJobPageState extends ConsumerState { final _formKey = GlobalKey(); String? _name; @@ -49,56 +42,39 @@ class _EditJobPageState extends ConsumerState { Future _submit() async { if (_validateAndSaveForm()) { - try { - final database = ref.read(databaseProvider)!; - final jobs = await database.jobsStream().first; - final allLowerCaseNames = - jobs.map((job) => job.name.toLowerCase()).toList(); - if (widget.job != null) { - allLowerCaseNames.remove(widget.job!.name.toLowerCase()); - } - if (allLowerCaseNames.contains(_name?.toLowerCase())) { - unawaited(showAlertDialog( - context: context, - title: 'Name already used', - content: 'Please choose a different job name', - defaultActionText: 'OK', - )); - } else { - final id = widget.job?.id ?? documentIdFromCurrentDate(); - final job = - Job(id: id, name: _name ?? '', ratePerHour: _ratePerHour ?? 0); - await database.setJob(job); - Navigator.of(context).pop(); - } - } catch (e) { - unawaited(showExceptionAlertDialog( - context: context, - title: 'Operation failed', - exception: e, - )); + final success = + await ref.read(editJobScreenControllerProvider.notifier).submit( + job: widget.job, + name: _name ?? '', + ratePerHour: _ratePerHour ?? 0, + ); + if (success && mounted) { + context.pop(); } } } @override Widget build(BuildContext context) { + ref.listen( + editJobScreenControllerProvider, + (_, state) => state.showAlertDialogOnError(context), + ); + final state = ref.watch(editJobScreenControllerProvider); return Scaffold( appBar: AppBar( - elevation: 2.0, title: Text(widget.job == null ? 'New Job' : 'Edit Job'), actions: [ TextButton( + onPressed: state.isLoading ? null : _submit, child: const Text( 'Save', style: TextStyle(fontSize: 18, color: Colors.white), ), - onPressed: () => _submit(), ), ], ), body: _buildContents(), - backgroundColor: Colors.grey[200], ); } diff --git a/lib/src/features/jobs/presentation/edit_job_screen/edit_job_screen_controller.dart b/lib/src/features/jobs/presentation/edit_job_screen/edit_job_screen_controller.dart new file mode 100644 index 00000000..6f65055e --- /dev/null +++ b/lib/src/features/jobs/presentation/edit_job_screen/edit_job_screen_controller.dart @@ -0,0 +1,48 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/data/firestore_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/presentation/edit_job_screen/job_submit_exception.dart'; + +class EditJobScreenController extends AutoDisposeAsyncNotifier { + @override + FutureOr build() { + // ok to leave this empty if the return type is FutureOr + } + + Future submit( + {Job? job, required String name, required int ratePerHour}) async { + final currentUser = ref.read(authRepositoryProvider).currentUser; + if (currentUser == null) { + throw AssertionError('User can\'t be null'); + } + // set loading state + state = const AsyncLoading().copyWithPrevious(state); + // check if name is already in use + final database = ref.read(databaseProvider); + final jobs = await database.fetchJobs(uid: currentUser.uid); + final allLowerCaseNames = + jobs.map((job) => job.name.toLowerCase()).toList(); + if (job != null) { + allLowerCaseNames.remove(job.name.toLowerCase()); + } + // check if name is already used + if (allLowerCaseNames.contains(name.toLowerCase())) { + state = AsyncError(JobSubmitException(), StackTrace.current); + return false; + } else { + final id = job?.id ?? documentIdFromCurrentDate(); + final updated = Job(id: id, name: name, ratePerHour: ratePerHour); + state = await AsyncValue.guard( + () => database.setJob(uid: currentUser.uid, job: updated), + ); + return state.hasError == false; + } + } +} + +final editJobScreenControllerProvider = + AutoDisposeAsyncNotifierProvider( + EditJobScreenController.new); diff --git a/lib/src/features/jobs/presentation/edit_job_screen/job_submit_exception.dart b/lib/src/features/jobs/presentation/edit_job_screen/job_submit_exception.dart new file mode 100644 index 00000000..e8f19329 --- /dev/null +++ b/lib/src/features/jobs/presentation/edit_job_screen/job_submit_exception.dart @@ -0,0 +1,9 @@ +class JobSubmitException { + String get title => 'Name already used'; + String get description => 'Please choose a different job name'; + + @override + String toString() { + return '$title. $description.'; + } +} diff --git a/lib/app/home/job_entries/entry_page.dart b/lib/src/features/jobs/presentation/entry_screen/entry_screen.dart similarity index 68% rename from lib/app/home/job_entries/entry_page.dart rename to lib/src/features/jobs/presentation/entry_screen/entry_screen.dart index 958dc7ea..c8aa219e 100644 --- a/lib/app/home/job_entries/entry_page.dart +++ b/lib/src/features/jobs/presentation/entry_screen/entry_screen.dart @@ -1,38 +1,27 @@ -import 'package:flutter/cupertino.dart'; +import 'dart:async'; + import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:starter_architecture_flutter_firebase/app/top_level_providers.dart'; -import 'package:starter_architecture_flutter_firebase/common_widgets/date_time_picker.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/job_entries/format.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/entry.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/job.dart'; -import 'package:alert_dialogs/alert_dialogs.dart'; -import 'package:starter_architecture_flutter_firebase/routing/app_router.dart'; -import 'package:starter_architecture_flutter_firebase/services/firestore_database.dart'; -import 'package:pedantic/pedantic.dart'; +import 'package:go_router/go_router.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/date_time_picker.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/data/firestore_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/entry.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/presentation/entry_screen/entry_screen_controller.dart'; +import 'package:starter_architecture_flutter_firebase/src/utils/async_value_ui.dart'; +import 'package:starter_architecture_flutter_firebase/src/utils/format.dart'; -class EntryPage extends ConsumerStatefulWidget { - const EntryPage({required this.job, this.entry}); - final Job job; +class EntryScreen extends ConsumerStatefulWidget { + const EntryScreen({super.key, required this.jobId, this.entryId, this.entry}); + final JobID jobId; + final EntryID? entryId; final Entry? entry; - static Future show( - {required BuildContext context, required Job job, Entry? entry}) async { - await Navigator.of(context, rootNavigator: true).pushNamed( - AppRoutes.entryPage, - arguments: { - 'job': job, - 'entry': entry, - }, - ); - } - @override - _EntryPageState createState() => _EntryPageState(); + ConsumerState createState() => _EntryPageState(); } -class _EntryPageState extends ConsumerState { +class _EntryPageState extends ConsumerState { late DateTime _startDate; late TimeOfDay _startTime; late DateTime _endDate; @@ -61,7 +50,7 @@ class _EntryPageState extends ConsumerState { final id = widget.entry?.id ?? documentIdFromCurrentDate(); return Entry( id: id, - jobId: widget.job.id, + jobId: widget.jobId, start: start, end: end, comment: _comment, @@ -69,26 +58,23 @@ class _EntryPageState extends ConsumerState { } Future _setEntryAndDismiss() async { - try { - final database = ref.read(databaseProvider)!; - final entry = _entryFromState(); - await database.setEntry(entry); - Navigator.of(context).pop(); - } catch (e) { - unawaited(showExceptionAlertDialog( - context: context, - title: 'Operation failed', - exception: e, - )); + final entry = _entryFromState(); + final success = + await ref.read(entryScreenControllerProvider.notifier).setEntry(entry); + if (success && mounted) { + context.pop(); } } @override Widget build(BuildContext context) { + ref.listen( + entryScreenControllerProvider, + (_, state) => state.showAlertDialogOnError(context), + ); return Scaffold( appBar: AppBar( - elevation: 2.0, - title: Text(widget.job.name), + title: Text(widget.entry != null ? 'Edit Entry' : 'New Entry'), actions: [ TextButton( child: Text( diff --git a/lib/src/features/jobs/presentation/entry_screen/entry_screen_controller.dart b/lib/src/features/jobs/presentation/entry_screen/entry_screen_controller.dart new file mode 100644 index 00000000..7bd91d79 --- /dev/null +++ b/lib/src/features/jobs/presentation/entry_screen/entry_screen_controller.dart @@ -0,0 +1,29 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/data/firestore_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/entry.dart'; + +class EntryScreenController extends AutoDisposeAsyncNotifier { + @override + FutureOr build() { + // ok to leave this empty if the return type is FutureOr + } + + Future setEntry(Entry entry) async { + final currentUser = ref.read(authRepositoryProvider).currentUser; + if (currentUser == null) { + throw AssertionError('User can\'t be null'); + } + final database = ref.read(databaseProvider); + state = const AsyncLoading(); + state = await AsyncValue.guard( + () => database.setEntry(uid: currentUser.uid, entry: entry)); + return state.hasError == false; + } +} + +final entryScreenControllerProvider = + AutoDisposeAsyncNotifierProvider( + EntryScreenController.new); diff --git a/lib/app/home/job_entries/entry_list_item.dart b/lib/src/features/jobs/presentation/job_entries_screen/entry_list_item.dart similarity index 91% rename from lib/app/home/job_entries/entry_list_item.dart rename to lib/src/features/jobs/presentation/job_entries_screen/entry_list_item.dart index a508a682..76903629 100644 --- a/lib/app/home/job_entries/entry_list_item.dart +++ b/lib/src/features/jobs/presentation/job_entries_screen/entry_list_item.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/job_entries/format.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/entry.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/job.dart'; +import 'package:starter_architecture_flutter_firebase/src/utils/format.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/entry.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart'; class EntryListItem extends StatelessWidget { const EntryListItem({ + super.key, required this.entry, required this.job, this.onTap, @@ -77,6 +78,7 @@ class EntryListItem extends StatelessWidget { class DismissibleEntryListItem extends StatelessWidget { const DismissibleEntryListItem({ + super.key, required this.dismissibleKey, required this.entry, required this.job, diff --git a/lib/src/features/jobs/presentation/job_entries_screen/job_entries_list.dart b/lib/src/features/jobs/presentation/job_entries_screen/job_entries_list.dart new file mode 100644 index 00000000..ab4b1a05 --- /dev/null +++ b/lib/src/features/jobs/presentation/job_entries_screen/job_entries_list.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/list_items_builder.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/data/firestore_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/entry.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/presentation/job_entries_screen/entry_list_item.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/presentation/job_entries_screen/job_entries_list_controller.dart'; +import 'package:starter_architecture_flutter_firebase/src/routing/app_router.dart'; +import 'package:starter_architecture_flutter_firebase/src/utils/async_value_ui.dart'; + +class JobEntriesList extends ConsumerWidget { + const JobEntriesList({super.key, required this.job}); + final Job job; + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen( + jobsEntriesListControllerProvider, + (_, state) => state.showAlertDialogOnError(context), + ); + final entriesStream = ref.watch(jobEntriesStreamProvider(job)); + return ListItemsBuilder( + data: entriesStream, + itemBuilder: (context, entry) { + return DismissibleEntryListItem( + dismissibleKey: Key('entry-${entry.id}'), + entry: entry, + job: job, + onDismissed: () => ref + .read(jobsEntriesListControllerProvider.notifier) + .deleteEntry(entry), + onTap: () => context.goNamed( + AppRoute.entry.name, + params: {'id': job.id, 'eid': entry.id}, + extra: entry, + ), + ); + }, + ); + } +} diff --git a/lib/src/features/jobs/presentation/job_entries_screen/job_entries_list_controller.dart b/lib/src/features/jobs/presentation/job_entries_screen/job_entries_list_controller.dart new file mode 100644 index 00000000..9423382c --- /dev/null +++ b/lib/src/features/jobs/presentation/job_entries_screen/job_entries_list_controller.dart @@ -0,0 +1,28 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/data/firestore_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/entry.dart'; + +class JobsEntriesListController extends AutoDisposeAsyncNotifier { + @override + FutureOr build() { + // ok to leave this empty if the return type is FutureOr + } + + Future deleteEntry(Entry entry) async { + final currentUser = ref.read(authRepositoryProvider).currentUser; + if (currentUser == null) { + throw AssertionError('User can\'t be null'); + } + final database = ref.read(databaseProvider); + state = const AsyncLoading(); + state = await AsyncValue.guard( + () => database.deleteEntry(uid: currentUser.uid, entry: entry)); + } +} + +final jobsEntriesListControllerProvider = + AutoDisposeAsyncNotifierProvider( + JobsEntriesListController.new); diff --git a/lib/src/features/jobs/presentation/job_entries_screen/job_entries_screen.dart b/lib/src/features/jobs/presentation/job_entries_screen/job_entries_screen.dart new file mode 100644 index 00000000..be0c3232 --- /dev/null +++ b/lib/src/features/jobs/presentation/job_entries_screen/job_entries_screen.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/async_value_widget.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/data/firestore_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/presentation/job_entries_screen/job_entries_list.dart'; +import 'package:starter_architecture_flutter_firebase/src/routing/app_router.dart'; + +class JobEntriesScreen extends ConsumerWidget { + const JobEntriesScreen({super.key, required this.jobId}); + final JobID jobId; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final jobAsync = ref.watch(jobStreamProvider(jobId)); + return ScaffoldAsyncValueWidget( + value: jobAsync, + data: (job) => JobEntriesPageContents(job: job), + ); + } +} + +class JobEntriesPageContents extends StatelessWidget { + const JobEntriesPageContents({super.key, required this.job}); + final Job job; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(job.name), + actions: [ + IconButton( + icon: const Icon(Icons.edit, color: Colors.white), + onPressed: () => context.goNamed( + AppRoute.editJob.name, + params: {'id': job.id}, + extra: job, + ), + ), + ], + ), + body: JobEntriesList(job: job), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.add, color: Colors.white), + onPressed: () => context.goNamed( + AppRoute.addEntry.name, + params: {'id': job.id}, + extra: job, + ), + ), + ); + } +} diff --git a/lib/src/features/jobs/presentation/jobs_screen/jobs_screen.dart b/lib/src/features/jobs/presentation/jobs_screen/jobs_screen.dart new file mode 100644 index 00000000..515ca8fd --- /dev/null +++ b/lib/src/features/jobs/presentation/jobs_screen/jobs_screen.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:starter_architecture_flutter_firebase/src/constants/strings.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/data/firestore_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/list_items_builder.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/presentation/jobs_screen/jobs_screen_controller.dart'; +import 'package:starter_architecture_flutter_firebase/src/routing/app_router.dart'; +import 'package:starter_architecture_flutter_firebase/src/utils/async_value_ui.dart'; + +class JobsScreen extends StatelessWidget { + const JobsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text(Strings.jobs), + actions: [ + IconButton( + icon: const Icon(Icons.add, color: Colors.white), + onPressed: () => context.goNamed(AppRoute.addJob.name), + ), + ], + ), + body: Consumer( + builder: (context, ref, child) { + ref.listen( + jobsScreenControllerProvider, + (_, state) => state.showAlertDialogOnError(context), + ); + // * TODO: investigate why we get a dismissible error if we call + // * ref.watch(jobsScreenControllerProvider) here + final jobsAsyncValue = ref.watch(jobsStreamProvider); + return ListItemsBuilder( + data: jobsAsyncValue, + itemBuilder: (context, job) => Dismissible( + key: Key('job-${job.id}'), + background: Container(color: Colors.red), + direction: DismissDirection.endToStart, + onDismissed: (direction) => ref + .read(jobsScreenControllerProvider.notifier) + .deleteJob(job), + child: JobListTile( + job: job, + onTap: () => context.goNamed( + AppRoute.job.name, + params: {'id': job.id}, + ), + ), + ), + ); + }, + ), + ); + } +} + +class JobListTile extends StatelessWidget { + const JobListTile({Key? key, required this.job, this.onTap}) + : super(key: key); + final Job job; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text(job.name), + trailing: const Icon(Icons.chevron_right), + onTap: onTap, + ); + } +} diff --git a/lib/src/features/jobs/presentation/jobs_screen/jobs_screen_controller.dart b/lib/src/features/jobs/presentation/jobs_screen/jobs_screen_controller.dart new file mode 100644 index 00000000..5a1647ae --- /dev/null +++ b/lib/src/features/jobs/presentation/jobs_screen/jobs_screen_controller.dart @@ -0,0 +1,28 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/data/firestore_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart'; + +class JobsScreenController extends AutoDisposeAsyncNotifier { + @override + FutureOr build() { + // ok to leave this empty if the return type is FutureOr + } + + Future deleteJob(Job job) async { + final currentUser = ref.read(authRepositoryProvider).currentUser; + if (currentUser == null) { + throw AssertionError('User can\'t be null'); + } + final database = ref.read(databaseProvider); + state = const AsyncLoading(); + state = await AsyncValue.guard( + () => database.deleteJob(uid: currentUser.uid, job: job)); + } +} + +final jobsScreenControllerProvider = + AutoDisposeAsyncNotifierProvider( + JobsScreenController.new); diff --git a/lib/services/shared_preferences_service.dart b/lib/src/features/onboarding/data/onboarding_repository.dart similarity index 68% rename from lib/services/shared_preferences_service.dart rename to lib/src/features/onboarding/data/onboarding_repository.dart index a90fae8b..87e7f01e 100644 --- a/lib/services/shared_preferences_service.dart +++ b/lib/src/features/onboarding/data/onboarding_repository.dart @@ -1,11 +1,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; -final sharedPreferencesServiceProvider = - Provider((ref) => throw UnimplementedError()); - -class SharedPreferencesService { - SharedPreferencesService(this.sharedPreferences); +class OnboardingRepository { + OnboardingRepository(this.sharedPreferences); final SharedPreferences sharedPreferences; static const onboardingCompleteKey = 'onboardingComplete'; @@ -17,3 +14,6 @@ class SharedPreferencesService { bool isOnboardingComplete() => sharedPreferences.getBool(onboardingCompleteKey) ?? false; } + +final onboardingRepositoryProvider = + Provider((ref) => throw UnimplementedError()); diff --git a/lib/src/features/onboarding/presentation/onboarding_controller.dart b/lib/src/features/onboarding/presentation/onboarding_controller.dart new file mode 100644 index 00000000..0b0c88c0 --- /dev/null +++ b/lib/src/features/onboarding/presentation/onboarding_controller.dart @@ -0,0 +1,21 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/onboarding/data/onboarding_repository.dart'; + +class OnboardingController extends AutoDisposeAsyncNotifier { + @override + FutureOr build() { + // no op + } + + Future completeOnboarding() async { + final onboardingRepository = ref.watch(onboardingRepositoryProvider); + state = const AsyncLoading(); + state = await AsyncValue.guard(onboardingRepository.setOnboardingComplete); + } +} + +final onboardingControllerProvider = + AutoDisposeAsyncNotifierProvider( + OnboardingController.new); diff --git a/lib/src/features/onboarding/presentation/onboarding_screen.dart b/lib/src/features/onboarding/presentation/onboarding_screen.dart new file mode 100644 index 00000000..fc9e5943 --- /dev/null +++ b/lib/src/features/onboarding/presentation/onboarding_screen.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:starter_architecture_flutter_firebase/src/common_widgets/primary_button.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/onboarding/presentation/onboarding_controller.dart'; +import 'package:starter_architecture_flutter_firebase/src/localization/string_hardcoded.dart'; +import 'package:starter_architecture_flutter_firebase/src/routing/app_router.dart'; + +class OnboardingScreen extends ConsumerWidget { + const OnboardingScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(onboardingControllerProvider); + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + 'Track your time.\nBecause time counts.', + style: Theme.of(context).textTheme.headline5, + textAlign: TextAlign.center, + ), + FractionallySizedBox( + widthFactor: 0.5, + child: SvgPicture.asset('assets/time-tracking.svg', + semanticsLabel: 'Time tracking logo'), + ), + PrimaryButton( + text: 'Get Started'.hardcoded, + isLoading: state.isLoading, + onPressed: state.isLoading + ? null + : () async { + await ref + .read(onboardingControllerProvider.notifier) + .completeOnboarding(); + // TODO: Check if mounted + // go to sign in page after completing onboarding + context.goNamed(AppRoute.signIn.name); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/localization/string_hardcoded.dart b/lib/src/localization/string_hardcoded.dart new file mode 100644 index 00000000..f064ec8b --- /dev/null +++ b/lib/src/localization/string_hardcoded.dart @@ -0,0 +1,5 @@ +/// A simple placeholder that can be used to search all the hardcoded strings +/// in the code (useful to identify strings that need to be localized). +extension StringHardcoded on String { + String get hardcoded => this; +} diff --git a/lib/src/routing/app_router.dart b/lib/src/routing/app_router.dart new file mode 100644 index 00000000..de559027 --- /dev/null +++ b/lib/src/routing/app_router.dart @@ -0,0 +1,208 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/account/account_screen.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/email_password/email_password_sign_in_form_type.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/email_password/email_password_sign_in_screen.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/sign_in/sign_in_screen.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/entries/presentation/entries_screen.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/entry.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/presentation/entry_screen/entry_screen.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/presentation/job_entries_screen/job_entries_screen.dart'; +import 'package:go_router/go_router.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/presentation/edit_job_screen/edit_job_screen.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/presentation/jobs_screen/jobs_screen.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/onboarding/data/onboarding_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/onboarding/presentation/onboarding_screen.dart'; +import 'package:starter_architecture_flutter_firebase/src/routing/go_router_refresh_stream.dart'; +import 'package:starter_architecture_flutter_firebase/src/routing/scaffold_with_bottom_nav_bar.dart'; + +// private navigators +final _rootNavigatorKey = GlobalKey(); +final _shellNavigatorKey = GlobalKey(); + +enum AppRoute { + onboarding, + signIn, + emailPassword, + jobs, + job, + addJob, + editJob, + entry, + addEntry, + editEntry, + entries, + account, +} + +final goRouterProvider = Provider((ref) { + final authRepository = ref.watch(authRepositoryProvider); + final onboardingRepository = ref.watch(onboardingRepositoryProvider); + return GoRouter( + initialLocation: '/signIn', + navigatorKey: _rootNavigatorKey, + debugLogDiagnostics: true, + redirect: (context, state) { + final didCompleteOnboarding = onboardingRepository.isOnboardingComplete(); + if (!didCompleteOnboarding) { + // Always check state.subloc before returning a non-null route + // https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/redirection.dart#L78 + if (state.subloc != '/onboarding') { + return '/onboarding'; + } + } + final isLoggedIn = authRepository.currentUser != null; + if (isLoggedIn) { + if (state.subloc.startsWith('/signIn')) { + return '/jobs'; + } + } else { + if (state.subloc.startsWith('/jobs') || + state.subloc.startsWith('/entries') || + state.subloc.startsWith('/account')) { + return '/signIn'; + } + } + return null; + }, + refreshListenable: GoRouterRefreshStream(authRepository.authStateChanges()), + routes: [ + GoRoute( + path: '/onboarding', + name: AppRoute.onboarding.name, + pageBuilder: (context, state) => NoTransitionPage( + key: state.pageKey, + child: const OnboardingScreen(), + ), + ), + GoRoute( + path: '/signIn', + name: AppRoute.signIn.name, + pageBuilder: (context, state) => NoTransitionPage( + key: state.pageKey, + child: const SignInScreen(), + ), + routes: [ + GoRoute( + path: 'emailPassword', + name: AppRoute.emailPassword.name, + pageBuilder: (context, state) => MaterialPage( + key: state.pageKey, + fullscreenDialog: true, + child: const EmailPasswordSignInScreen( + formType: EmailPasswordSignInFormType.signIn, + ), + ), + ), + ], + ), + ShellRoute( + navigatorKey: _shellNavigatorKey, + builder: (context, state, child) { + return ScaffoldWithBottomNavBar(child: child); + }, + routes: [ + GoRoute( + path: '/jobs', + name: AppRoute.jobs.name, + pageBuilder: (context, state) => NoTransitionPage( + key: state.pageKey, + child: const JobsScreen(), + ), + routes: [ + GoRoute( + path: 'add', + name: AppRoute.addJob.name, + parentNavigatorKey: _rootNavigatorKey, + pageBuilder: (context, state) { + return MaterialPage( + key: state.pageKey, + fullscreenDialog: true, + child: const EditJobScreen(), + ); + }, + ), + GoRoute( + path: ':id', + name: AppRoute.job.name, + pageBuilder: (context, state) { + final id = state.params['id']!; + return MaterialPage( + key: state.pageKey, + child: JobEntriesScreen(jobId: id), + ); + }, + routes: [ + GoRoute( + path: 'entries/add', + name: AppRoute.addEntry.name, + parentNavigatorKey: _rootNavigatorKey, + pageBuilder: (context, state) { + final jobId = state.params['id']!; + return MaterialPage( + key: state.pageKey, + fullscreenDialog: true, + child: EntryScreen( + jobId: jobId, + ), + ); + }, + ), + GoRoute( + path: 'entries/:eid', + name: AppRoute.entry.name, + pageBuilder: (context, state) { + final jobId = state.params['id']!; + final entryId = state.params['eid']!; + final entry = state.extra as Entry?; + return MaterialPage( + key: state.pageKey, + child: EntryScreen( + jobId: jobId, + entryId: entryId, + entry: entry, + ), + ); + }, + ), + GoRoute( + path: 'edit', + name: AppRoute.editJob.name, + pageBuilder: (context, state) { + final jobId = state.params['id']; + final job = state.extra as Job?; + return MaterialPage( + key: state.pageKey, + fullscreenDialog: true, + child: EditJobScreen(jobId: jobId, job: job), + ); + }, + ), + ], + ), + ], + ), + GoRoute( + path: '/entries', + name: AppRoute.entries.name, + pageBuilder: (context, state) => NoTransitionPage( + key: state.pageKey, + child: const EntriesScreen(), + ), + ), + GoRoute( + path: '/account', + name: AppRoute.account.name, + pageBuilder: (context, state) => NoTransitionPage( + key: state.pageKey, + child: const AccountScreen(), + ), + ), + ], + ), + ], + //errorBuilder: (context, state) => const NotFoundScreen(), + ); +}); diff --git a/lib/src/routing/go_router_refresh_stream.dart b/lib/src/routing/go_router_refresh_stream.dart new file mode 100644 index 00000000..8c9c433b --- /dev/null +++ b/lib/src/routing/go_router_refresh_stream.dart @@ -0,0 +1,21 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; + +/// This class was imported from the migration guide for GoRouter 5.0 +class GoRouterRefreshStream extends ChangeNotifier { + GoRouterRefreshStream(Stream stream) { + notifyListeners(); + _subscription = stream.asBroadcastStream().listen( + (dynamic _) => notifyListeners(), + ); + } + + late final StreamSubscription _subscription; + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } +} diff --git a/lib/src/routing/not_found_screen.dart b/lib/src/routing/not_found_screen.dart new file mode 100644 index 00000000..55b38f82 --- /dev/null +++ b/lib/src/routing/not_found_screen.dart @@ -0,0 +1,18 @@ +// import 'package:ecommerce_app/src/localization/string_hardcoded.dart'; +// import 'package:ecommerce_app/src/common_widgets/empty_placeholder_widget.dart'; +// import 'package:flutter/material.dart'; + +// /// Simple not found screen used for 404 errors (page not found on web) +// class NotFoundScreen extends StatelessWidget { +// const NotFoundScreen({super.key}); + +// @override +// Widget build(BuildContext context) { +// return Scaffold( +// appBar: AppBar(), +// body: EmptyPlaceholderWidget( +// message: '404 - Page not found!'.hardcoded, +// ), +// ); +// } +// } diff --git a/lib/src/routing/scaffold_with_bottom_nav_bar.dart b/lib/src/routing/scaffold_with_bottom_nav_bar.dart new file mode 100644 index 00000000..e27db944 --- /dev/null +++ b/lib/src/routing/scaffold_with_bottom_nav_bar.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:starter_architecture_flutter_firebase/src/localization/string_hardcoded.dart'; +import 'package:starter_architecture_flutter_firebase/src/routing/app_router.dart'; + +// This is a temporary implementation +// TODO: Implement a better solution once this PR is merged: +// https://github.com/flutter/packages/pull/2650 +class ScaffoldWithBottomNavBar extends StatefulWidget { + const ScaffoldWithBottomNavBar({Key? key, required this.child}) + : super(key: key); + final Widget child; + + @override + State createState() => + _ScaffoldWithBottomNavBarState(); +} + +class _ScaffoldWithBottomNavBarState extends State { + // used for the currentIndex argument of BottomNavigationBar + int _selectedIndex = 0; + + void _tap(BuildContext context, int index) { + if (index == _selectedIndex) { + // If the tab hasn't changed, do nothing + return; + } + setState(() => _selectedIndex = index); + if (index == 0) { + // Note: this won't remember the previous state of the route + // More info here: + // https://github.com/flutter/flutter/issues/99124 + context.goNamed(AppRoute.jobs.name); + } else if (index == 1) { + context.goNamed(AppRoute.entries.name); + } else if (index == 2) { + context.goNamed(AppRoute.account.name); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: widget.child, + bottomNavigationBar: BottomNavigationBar( + type: BottomNavigationBarType.fixed, + currentIndex: _selectedIndex, + items: [ + // products + BottomNavigationBarItem( + icon: const Icon(Icons.work), + label: 'Jobs'.hardcoded, + ), + BottomNavigationBarItem( + icon: const Icon(Icons.view_headline), + label: 'Entries'.hardcoded, + ), + BottomNavigationBarItem( + icon: const Icon(Icons.person), + label: 'Account'.hardcoded, + ), + ], + onTap: (index) => _tap(context, index), + ), + ); + } +} diff --git a/lib/src/utils/alert_dialogs.dart b/lib/src/utils/alert_dialogs.dart new file mode 100644 index 00000000..6c26926c --- /dev/null +++ b/lib/src/utils/alert_dialogs.dart @@ -0,0 +1,12 @@ +library alert_dialogs; + +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/services.dart'; + +part 'show_alert_dialog.dart'; +part 'show_exception_alert_dialog.dart'; diff --git a/lib/src/utils/async_value_ui.dart b/lib/src/utils/async_value_ui.dart new file mode 100644 index 00000000..4fc1f997 --- /dev/null +++ b/lib/src/utils/async_value_ui.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:starter_architecture_flutter_firebase/src/localization/string_hardcoded.dart'; +import 'package:starter_architecture_flutter_firebase/src/utils/alert_dialogs.dart'; + +extension AsyncValueUI on AsyncValue { + void showAlertDialogOnError(BuildContext context) { + debugPrint('isLoading: $isLoading, hasError: $hasError'); + if (!isLoading && hasError) { + final message = error.toString(); + showExceptionAlertDialog( + context: context, + title: 'Error'.hardcoded, + exception: message, + ); + } + } +} diff --git a/lib/app/home/job_entries/format.dart b/lib/src/utils/format.dart similarity index 100% rename from lib/app/home/job_entries/format.dart rename to lib/src/utils/format.dart diff --git a/lib/src/utils/show_alert_dialog.dart b/lib/src/utils/show_alert_dialog.dart new file mode 100644 index 00000000..2bad9358 --- /dev/null +++ b/lib/src/utils/show_alert_dialog.dart @@ -0,0 +1,48 @@ +part of alert_dialogs; + +Future showAlertDialog({ + required BuildContext context, + required String title, + String? content, + String? cancelActionText, + required String defaultActionText, +}) async { + if (kIsWeb || !Platform.isIOS) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(title), + content: content != null ? Text(content) : null, + actions: [ + if (cancelActionText != null) + TextButton( + child: Text(cancelActionText), + onPressed: () => Navigator.of(context).pop(false), + ), + TextButton( + child: Text(defaultActionText), + onPressed: () => Navigator.of(context).pop(true), + ), + ], + ), + ); + } + return showCupertinoDialog( + context: context, + builder: (context) => CupertinoAlertDialog( + title: Text(title), + content: content != null ? Text(content) : null, + actions: [ + if (cancelActionText != null) + CupertinoDialogAction( + child: Text(cancelActionText), + onPressed: () => Navigator.of(context).pop(false), + ), + CupertinoDialogAction( + child: Text(defaultActionText), + onPressed: () => Navigator.of(context).pop(true), + ), + ], + ), + ); +} diff --git a/lib/src/utils/show_exception_alert_dialog.dart b/lib/src/utils/show_exception_alert_dialog.dart new file mode 100644 index 00000000..0bbc6146 --- /dev/null +++ b/lib/src/utils/show_exception_alert_dialog.dart @@ -0,0 +1,42 @@ +part of alert_dialogs; + +Future showExceptionAlertDialog({ + required BuildContext context, + required String title, + required dynamic exception, +}) => + showAlertDialog( + context: context, + title: title, + content: _message(exception), + defaultActionText: 'OK', + ); + +String _message(dynamic exception) { + if (exception is FirebaseException) { + return exception.message ?? exception.toString(); + } + if (exception is PlatformException) { + return exception.message ?? exception.toString(); + } + return exception.toString(); +} + +// TODO: Revisit this +// NOTE: The full list of FirebaseAuth errors is stored here: +// https://github.com/firebase/firebase-ios-sdk/blob/2e77efd786e4895d50c3788371ec15980c729053/Firebase/Auth/Source/FIRAuthErrorUtils.m +// These are just the most relevant for email & password sign in: +// Map _errors = { +// 'ERROR_WEAK_PASSWORD': 'The password must be 8 characters long or more.', +// 'ERROR_INVALID_CREDENTIAL': 'The email address is badly formatted.', +// 'ERROR_EMAIL_ALREADY_IN_USE': +// 'The email address is already registered. Sign in instead?', +// 'ERROR_INVALID_EMAIL': 'The email address is badly formatted.', +// 'ERROR_WRONG_PASSWORD': 'The password is incorrect. Please try again.', +// 'ERROR_USER_NOT_FOUND': +// 'The email address is not registered. Need an account?', +// 'ERROR_TOO_MANY_REQUESTS': +// 'We have blocked all requests from this device due to unusual activity. Try again later.', +// 'ERROR_OPERATION_NOT_ALLOWED': +// 'This sign in method is not allowed. Please contact support.', +// }; diff --git a/media/application-layers.png b/media/application-layers.png deleted file mode 100644 index c8c3274a78e79728f4c01ed8485491df5881cf44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 133722 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≧cuP?nD^?^pdec9^buhV)hfcG9Q1#*utVCoWo^t$D z-12#EF%rRE){9aFPq@7JQ5{3WBs^Vfx9^?8V`@R>;?RGWV8}q?um&gXDGjFNtp==k zjmt4_*EBl*ct_Rb#O5J%@u`M3w4|iO>}aV;+B2lb7$LnOK$w{O{-n!%y*C^a_-z}@ zgBRGdj%bm@nnBO?#9C5XUQquceFf`e0=#aqONWytkIH`u!^YmAL^-wiHgpV<)gZ6A zj@#MGQ=Po?WW5ZD<&C-O=yjqMykQT5{T<>#qZM(&%zJNO&GI=S`~|p+%UBf8eoXTV~DF% zOlg(}a&0bJ@8-%Z*(VMyZ1EPuY{$mq@H{We{|-f=XUZGz5mf(+@>F34=n)k2t8oWz zxSfv)29^mo+U}&ezAVv~{2?u9w4G;{?UfzK{MY&p!z$OymuokmI53Rg>jR?nm$dL2 zb~}y3zW-Z721Y<-SOrC}X+5)+A2bR}FzaJk8CSEw}yf9sQm+2Pzuai;4 zXh^dvcenVM`@R&aF9#7;&9oZ)gte9t1VTHHy-RY*mV<3>ISSrU(@oE(Bua?p8Z#GT z>&%`_F2j@ZMN1a7*i=z<1F?;hg8x0&SN#%Vr6u6N4l ztI9<2b(S5Uy2g|oEP1538sId3EnFQKOf}gkAS>sxla>)qx2)X;>8u8LKwW0&ML_0G~(mwGi0mFbX!vv78lJyqEEz; zbaZsJ4%_na0z)sTv#L(8vIL$U5Es515#n>XU`JlAJ0I%8NyqZGv^)QFc0bBj=h9IL zZD!#P)x+1G`3Wi&5K)Xw>B=l=!Obg+j?t>@~(KjjzoSMKH)u=;Af^rxxHR z{Vc!%l}NXLC8fPc6}#-daC02z=Ajn?9k!xgG64Bw@KOwQ!sVt~E0aq~$tNV$~l7=b{9PT51dhd)0088zSu%X9VEf$QVa?qd_E;*X&iIR0O#zDnvMi+#F&4<>8%VC9~O5Ckq;dMKHZcUt24t zwMA~0k_d5bN)f9>GVvaM%AW}Nc@o*gkRaKNGZ*WxXLp8So+|oo(XuB2;Q=v2c*)Aw z%FDII#vf`JVcwy^06u?rSS1LlcZw(h*d-e^^Ax?WPc8EmQ(NCJ{xpaQ%fJw8nU|YRa+lg4K}z|iY%@uaTn(P< z8>f{b;vDT|52#`uWUp;B!G?*vF~XggN=FbG+ap5yAPW-?|3#?X*on8Zb2zr;I=S;{ zOI8;t8Iu3Y+VQr_5fx4F-Kh@zQwHpigcS5F@^qNaah?tNGRgNo2_<0vXPq;Kll97& zz#h-Q>pA0@4^>2L$+EIH8QJs%@;W1On3g<(&a_emx?<#G)z~>&$dyBixJxJ#dH_Uh z&oWH8Ou$x>);(t|&j9{5P&B5tkV!g_-mmus?ZL(s4Wxjjqk|6#Cn)VGID!y_5u zd{h?*L{NX=Vf3!c9Vt4R4>zFio=WJSpwiWxD55?6h)*=E)Dr_zJe6}}yI=JM?=i|~ zZ|acU^j9TD=GK#5)8qnMxp1$PNeGCCwqD$OXrZPUs|NI!73Y7JLow&q#fx|L1|QLf zI5{g-r;d?f7pnds30LT6?K%JgNC-N_)JWI^Xv(*Z8U*aHUoh~yU@u=^z}=^Jt_Cqh zdc0XrCYapq?bqfz9(SOXWf0wFz~BaQD?N7-|HF5{xi)z5;vU0)F~idtLK>#gR0WKOJ{4Olwn;pu;hGtOTeE3vp&rrv9;vqwbt zN!+1mjbLDD`ANM8%3}GSWw9XjP1T-{A#3}R8ybwC2Rvb>L0oyw2EZ0yFK!DvhPN=R*moHO&B5t6$_!p zh6~|Qkaq6*xkXRI&ft6X%DKDNGHW5 z%vq1X9fO&wFY`Qgi#BjM+a4R^K5BSRpd3q~@a$nst0jjEfxHJtNx=1a9x9>q`=+xR z+{Yz+7CSsSq#!dIkEtd&%D3Je=98pkRzUTHOrRpx>!9agsMh=^dArs7J=gn;Masu< zuQsolA8MFMTs#hAEP5XDJ$#|0H)PW1^{LZbuwy>2-~>)ISI>lC{?;{NCNQqU1VifW z{Y^utjhG&sXbdwcSaGg9x=6a0#LNtymeDb}G+gdKIp!a_@=oGsKXe0&2^T%(teV&> zTLjNt9$P*4G%-Q&y5;V2~R@iozIUk2m&bZ(oHv|vk+@c;|(C+%z0 z!Ck)|s+a&iiEFewVPje{7{C)_;*+s9N)-V5!{fSv|E~TptuB~}AvTxR9P9%c8Q=lY zG9%%Mw~RWm5)9${CwN6L8_Us`1T`x;i^oeG52R`rdIk;x^EW(am@oRrxo%|b)k7{s z^Uny?f`SWLILQ~~mq(e}>r~`tM(cShSM6LfqHEJbs!d7SNgZ_KdXDcfR*(6+u3Bqo zwpeK|H=KsG?n-KaY`>MC;A@De<`V2Zt#HklS~?Fx_E%MhpQj5qrt=m;iH-^CxX$zz z^V{XcGbo^4a`GiRwDWtd(lJ94?Kmy7d!M2xw! zKQ~<^TW?oTzQ)j73GZ#C2cKBQ9So|al;Xjtp3+66M?lF0GJT!x)ky{cD&KekUfe9aS1;|P z0{@pC>tmG+bMRAhx-vdIPCA6+!6U^2Ncw=jga@b6b2W!mRlkFi}GhZ3lEdnvN_D7n&Y7lXf=jtCn>xv;5 zCu&R2nIb~;d5)KDSmt%%gR_cnpn%+a(o=J%5LCC{n0`#$6 zkHec27V&`p5mLOk;CiB=+G<-sSVs1*fv5Y>11B1q+*D>Egzmz@M(qFPFnk4j0KV|5 zJ-R7_`$@2^*s%sM>>e-SUULn%#~y?mSRoAyuPw2dydEhu zDqdc6OPY6nUD3+8+RBek0j!F$u*~k5+NEYRmqPUd$b1@mUEN?yTwX7K(XKI^$M5zi z$-^r;y3%+TKrU<7XD^?jrSB=a2jf6LkH3agjZ?)dj~~1r@~pnxcf>Hc7OkJo(_F6lgJz zaXda7cd5S`POOgJEjV^FGTY9-b7}B&TPJ5yh0jLC-qacR4LHggQlsO zZ8mWDcDi%yo%1zgCHG#C-3BIr|GMe?8|`Nkd9GOD%-a;|c+XpdkmZMWWZ0b3X0%q4P&Vu5XK5nymfnnEt7kmYykMey*9Gea zpfA>KKaQk8N&tOgkIfy^JZ%=xmxNpd{9V9FlROAPUG21EpB>B(YLg;#SY%S*2_j6o z;nfkrTNZ;ii5M)}vUOmd|&P6SPw{}UrwTviWWN}8ls+plJ z>~8e|>2G^Xjmr!WHa`^N!5_@XPW;*=9I@zIsg2*NW?e;(ZN|~IFNXrbtbIUNU%5=LocxAnmil%h0IAJt3e!ND- z0+|v$Q+i~7den@wKrU14WuJ~T?!}g{B#mjD%59CX!eZ;I%=5E}k+n^U2`*;$q3IX&m9MqQVE1f`W{e#CHAZuaq~W7Pc7u=au*_<#OCa;QE{O2zo9`*IK&x7`+ul5>(0F;g z3?+DJo%(hBuqe*Mh@3?MNBro|a+zO;sxF1@L}dVlx33ot>oH$xMiYAKxRDN=AKxA{ zVJ3Mcxu2QY>K5`g<#2e4@m*8dUP+& zncCxz(0-=P>3*dUQ|I0UC=YSdxnWl(6>2GG#hy}bp7RK6tz06%1JtVV)?mU%6rv|_ ze^G|l!Zb?mlrZ#ETlIujqD-TwR#~X&OKLHo?*aKVr2|kO#YR40CyfjGbwv;v5La<` z8__#&GxT^xm@2hQHE^T%0Z`9sy@@mU4zFiV-yQm%`d6dYDyIn$7H^3odEM@noqAa` zG>4L`-c_eaDf%D3tK`!L=CSMb8IOhe@Or(2sE;|T6U}vFIhIO?WD_vA6G@35A2AGu zha7d(-BMCh&-P9R1yUk+4{m5+;*wg8EEAyAj;`wLX5cWN_VM>jWIzK!*Cu^Iyc59f zx{Ff>>Ji>!!!cf6cns8ghS?|hHi3l$zmYNO`!LL3b063}aI_EV@YXpa?4J<54oYOf zkGv`-)SWN?CLGl_5V&DE3sG0|L?6{kNJkF2?VLt#I{n-xdPj<%RZYDIS|1wpQ};iG)#&~<0!wpulU@zprdK{&xEZa--7BBw-&0;TYg2-U-(zkEi9F0< zXzplbO?K(EDwP}E7pUBl{gBV@*sl_}{|Hp@OAh+XTkx(X5aZ?Q1$s#JH95;b&+)BO zZ)$17`xgyZN#5V~f=;q3Mv*DPgUItzu3}Tvj=eHTtZ`9+2TX2VxHl__{hv@9x<#&H z25b#`AcDJaPp>+w8>BRgGbt>)Bcfid;UKRtaG(|`fBVd!>9 zW6BV+<>?6xs>v<5aLY;kD}kE^yF4eIjHf;G-kAc)`oSxzPH2(!r|VY%{9qFbsv35# zPtl97qyWa(>D`Le^|o6Q9y1&jk3utFQrYdQPm6#LHx1ONcF07g(w{=%GB;wLN^V8e zJxR1ZmE%)O2v0wo+2NI36Z%N0aXga0_b&D)yRS;1R|W+eqxkIY(SoJ0Oe1r~gW3gW zaNK&i(dRX?CP>A*i8Ov?ePOASwxToLA_@;EPFdyqbt6az&OL{tP_o|*JwT~reJ^N2 zO>HN#vNZU)cZnFbTLjnN)~6kiMLHTY(sh<6#}I3f61Y$NadS-rH#<^KY-YPcvFjDa z)5aKTC@|t8U&r#yU5)o7CAYep!YQgUu@hXo^g(KNZa49?JL-XH@H0*>|Rf^mOGJG*7=_sB~F~FckG-)#OW4V1%v}fG1!x!?S2e1qr2<6ZDM`D zKOHh3#LVz3-`))3iCtR6efKW@8j==q4gSsyi;|weRyP@x;qFHsB!c;i+df6IFB+}%??sV(z~5W4 zmjMpcu04G<7P!HtC1pteRDrOBJMIt*g}H-lkm>y5TvP7vo87`gwxc^UL;znTS`6*w zTB2yN?Yd>1jjmXVMPMmC`aX@(;tHg>B?nV9#(cN_0Bh-g z?@LcofDnS+mpoX*yy(Pl$xN&4-RSC7v1F|}#dfr-L9!x5T(hz)XB8<7GawpPt7?du zq4^{a^cky>#L!147i+!}Yh)Hk4$w=3CCfy>TCol(!UT#wH`T7Zy)Fvo(*?z<2#V&v z2^Cr7o;#cBQ^h9$#m4GPipiq;NzF7FCOOCku5A`@IfWIou>i;iKVJE^dFu^%Ngb&% ztMY+2+D~CA-%@W6BWUlP@PKWq=Y}+fTcV7JP8o*{*%ilBdlrO#x3?q1Lwre>P^<8w zDs;O?pt@9tU9y~vv+Yz%Z;Z%eyMFjBS%z0!34yo+rxPJB|UN6_8p3F~BH z1z8ZeYx0L_p5~P9aQoq{opFZh`i=N2dPy@^p-(JHw~=Vw~ogB8JS#OvG=;V z#@?HQTt}bYak@GWh>0y>#JXyIF+5Ns6)xy*mmC=H;7{rvLW|{8+)FgB=-5&#RA}BZ zuPof~c`UyLG;OKyo`aCdl&SsH3MNOZq`&vd&uXPOPYAK~tj}=XR1qY@YNcvo&K86X zk6u;Hl2CKXR8enO5@DX=%%-2d&Hlj^Rl_cW{t~b1ePLORh z`iG2@GK^-vtBn?HYk_EkB_qST~`Xhbr@U@O#qvmN`uU+OvbQ^~ocwQzkse<~GP+XUlH zzU<*L4-4fI!^dZfiR^=}?lY9_1=d^@M)Rob$D#*RyN!!f+p#_NM7@-v#b zepw1R6egZv^EOm;Gy;w52#LNJX-GvKtuZHq6<`8Gn}W!=AF5ohQ(!|H>aCT7j4vB} zkx}C2Z#feJy65*2f$)$Jq=%_1LwQAC%Qp!4`NSE0>HIxT)JWf*(dm~Cral_^!>cNt zFRfW&0fi-x(g!RzWinwU*Iie|`rFg94`E;G3i-mQH*tBkkIP(av_+gQ@Zjl^F7+KE zjQF?_4p!=80N|CehaFCuG&lrgWN1s11sQD%2M@uE{ks&3$*5l-U?G8WC>6u|xf;3~ zNABxJ+{v-dK^b=M*LJeH8Q;2el}e{fOLFifUjA()N72sP^!yo)^+e!a`MN>7jf38LYzc1FR^VYU)E{%*>N!q+IY(WqV9lte80nJ7H12 z>YjR>s*ebrWt3NdrkR(wk8?C0+3!`x6E?FeIXA_J1Y_Czhl)o;Mc#8J`*n3L>^XO& zZ#$T`J2+}fj3vcG=OS)87hSKYr{mh>p}}84*-nGEi}W5317Tj(c?*lK*n-%Z1T7M9 z%aP!)fHej$2{sj~SO^xGxxbBhi7kBYe`m2!A-c-_MMcFVx zjye&eC#;pXL5wFPqw42)-_w*ldVY1M6}Se3GC7mM-=FTGe$!mWim>IK^F?4zQ091Gm>ih|>trD)MQmc-&fu@8ipjcTs9j~tP z-Q8jxkjkp%5!Jx9ez}YuMS~<~)M3cB?nyc!$r(ywk+_MoXdm?&E|1rMj)dD*A4=(b zGu_tH+cCN?4};|2Ot*0tS{;l`T&jiLO?wRED61tigg@oeLb{nS*L{!MUKPsl>#eWF zP}%g+f0N|4YZJznL6fT2kUi7n3_lAqHwvuc@vb;B2WT^%Pn6EVm0a1?w?i@^rBcVq z!=#;ECuP$^Q(+yG2{*T)gTjPK{4BA0EmksB(q0AB`N#dgTR3_UI$k`#BNprZ!`!%f zRu(h?ud09AEpSA97UOaevz9f|+k~S8s`B{;KhS#r;&H@crg{arqMTDeGm+D&s#G3;C}peD%(9uKMsf-bl`QYNs95fZrN94k1e z#~xeX%jyzzc6BCasNq_PP2x{o753=Ph`D}C{kc)yV73bLN@O3A{fJw304MW<0Mtg( zO<;p@%*&Ex*?^X{ZdeESO#B(kgQK)DXVwKVOFgfIPx$V%;hCJv6gaQdx&2^Y5iPQ} z3ge$1!+rP)6*^hPIX%i1N1kcNp1j_>>C^WO#}?E9ZQ*%IwYOBAd>-X_^$FD zS2M)c1)K`@L-rvIZb_yfee{y;{BV`Xjfk3Wj@@yXI*?}$v~Y2wz)^B&5JxRX8UOyk z+nkx~!QOf!zMtUAKVVEa8fABO|CHTfFyGk<54v06JqBdpU-cQ&c}VrV#LT#@x({P? z({eMr@isdVrH8~mDj=Xw%*~dIy9cm26NUObxugX{!sc%_Iw6OG9!z}|v4w`914gW# zSCJ&R1^GO@=sREA4cR7$AHe#4@C8q1+nmjj>a={!j`6Irs8K*ZaO#myIxPnN+bup#yeTDfne%9FYxW1W4BS&X1qfEIid)(^ z{o|GgRjAI&d;u?L>Rc$`J8g11(EEz+0UNyipbrPOE{bVzoNis^F!fi%v=X#{fiBbg z6`q@Er?`@K4;uK!iZRQfGpMmQrnV#CufO2cJZ7D%xe`?Drvdh#h7o1Wvd}zA%`qrU z)yTH9z-7`@yM5I-RC7OVdHbD(21INK4=5M-YEz1R(R# z)6G93bDy5JwZHz#dTM9chnjU z16XUM&hNU7U7jbXw%Mm#mzFHkLzs=2VfNPWIh}#1$*f|tam%21eYTp@F+9Ipa(zZI zCSS$lBj0ORSY%2eRn%=-;ur+}tIiE6b>B)r>U&Su;pvmK%wPFEPzK^Is4fjd9zhj=PwO?oBiERDeE7P88aR4GBBYx$M$^hzoP+B)CH0u7 zMDCT;7TNKJE4l}lO?MZjX(tW%`paOf#5zYZd#LRX+2LP;N4yFR3-p@`F=7mwA;~wl z97;POksj`ZKtbb{BVK9VGlU+(YdtbEs+R}WaT#?C`2@~Jw4!df?envdJjpS5-5&!r zoV`w&KbUGnf#%o!Z9OuzhG)}{Zy_e)>~{^yjxbz zM#GyL_2tJPC)hD$=>*Gr{UfAm^Hb^*m$Z{h3G?JO1f5{kv$t2Pb8MxAF5sJ z`IFHVyyUr{ntHYqL2D_H%udmnJqNqS?A&s%pj89wVRrqDIJa4E_7vlV;$yh>?Y1@1 zf&~61`p%J6HvZxV6_;(lQ=|y+Qhj<3m1)`t=%;zyhEH<@Wpt>RF1;iHIL2bNg~~GF zrzo{htO$zZZdk*&YgGwrVEqy(`2lguHU9Wf2UXdqELvELdrK=MKDXm5m8#PbJf-40 zlIC>N-Ql>w6@;lE&Q*A-+l@@u%Zb*!rWQZ3zVE?&nQ_;o4X>O_PFWwfTcY4fLL+L; z4_79xYF_Sc>>-P~ggVn~C8xN16z;Rk2&Kdi?$^fHvR&Dp9)F>$4UP2dliA z8$sJLr1S9-pEOG+iSG{!P4^`F9qxmlw4@V;``y zKTT|y-*Zj4wY)AfL~z8LQ}DR5${Bq@hxM~CdIp{CK>p=$lPU8E7tm3QnopF0LM|@n zb5@8&m_^8Z0N+?n8vV@==u28h8rhce01L^|l5HH6CKTgineQLe`fl82q+Rf-Q^jM` z3|zcHu^anRsYT>%m``ObA*$B7yOFZIYfHz*^*n9~{jE3~>Bmr>dha~P5^$sqnae5# zu57;a(57cn7`iM=V|&1r;^^N4WWQ3n8DM-*%M@`H*J@DPV2N-3^1G?bj~^|n5C=Vb zLn-hD<+$qQxyG>N8-(*rs8{S%U}i%*5g1h09Z^2b0Qv#?B6 zWhQe8*;viRk6rxI)R2VY9EQwFV@N|Zu5SN|(qXjcT~*c!4@T#LzAq$$N@Gr`MLo z8nI3)Qz7*W?3Q`Mix1APEap^b+CB0@FPT|6_tLrw)$T{m=Sw8z`_yCh=|cAVe%$KM zGDwlXisZ;pb5YG5%lddl{@4xNRa@gh5pYg`N_iCIJ#Lk9(rPN1>CKp*11z&Y!l$!} zsq^5e({;es^r){!+uVBvkN-593OElB)igMLT{o+(#x~(X$hwn?3LXb5wn32*G*{DV zeDkGm`~}0kn-^<#e+_n=(S#^`rk4$YXLsB>*$0+D(T01DKm+EWJm+Fql;a!LRV8~R(X5!|hJo2;hD_!H)k3uQ zr(eW`r!Si-$zkM}qQ!f{3?m!7QxOtPR42EcdvePnWvNebJs4{^AJu=Z;9C@Lt%b+p zek#%544l7vF_-)1H3}3oqS^BNiYT2NPjR^$L(nTw)rW66uz2V_47vUE=DYcEwuqze zbHd`*oO&nWr@)4FlHaMRHaDL#+$YtF+LJ9pt&1FDWslm5q(kEmiUg3<(I!~BQ0i`TK|PGQ4fiV#GNiqyiO zAV0n!-XSK8^H+mk7Gm(WC%ukAKT)E|)JOVX}QJf2Q*WCVy*_Kdx)4 zbcpj`uU5Q({0haCV6-UTbFPR{8bR_P2?2f}AwVMvz{0uXspf>QrZ%mpHp015u^mOa z?&&iZ3BbgRbW+J{PA7?efqD3eyrr9D`C3U28i&))x3F^CyI(ffn`KQZWkagAVf!ix zgTy)Rd+ahM^4FqfK{WgL(VLt!0d*_sp6{*+@s+d6OVzPv1t+rjM({eBxE@(|qBaC8c&P6YCc(!*a*f(K~bb z5j`uo$%=#4E1%CR*zt6#1amMpu#YrW+~e)jeaf*b7+sK_ zbuD;mAgaiFXyxmB5tRS(Lufzy-Yw%Ci(+5&_Uy)Ww?_u>PPFva>?iTQ$my`lb4+&{ zUKW`O@Jux&S;M6?HjBX?X0@yvxMi`D{34FU0)J&)K%cHNxPsf^s`^%UF1{bR+ftVt z!j%+lvyQ%Hz_M=0E4<4P4pkS<2Bu&;UocIJW8|d5J1Rfb2|4L}^(y)F<)}iIy48xg z&&=sI#JRe&rb4tXh+lpUsjEI6Z&!V(tjl`q$l}%geEtJe94OO*55C=vhnc`a_4*IA zFlulR6&jNl$3qokI-R)!?Y+>V$w+TB@s;xd(-~ibzdu>HZ@0gqJiOFCJtG#29cFZk zZhnorQI-7lRr=%bYV+f29EDdXzk5@8%-Rquwvo z&GZ-1Fz=gKG5iClR4y?ohXMoq=jZ*i>xU7th1%W1<4Zun3H#vXD=2i6TKByE?=xI4BCa zk|Oh%zW*%qUu7;p8t_9c4B=3SnH|6ee)0eCg&H5w7jHyh7M~jmorZMmICo!p^|&ZnbipyI=kN-5v!#ld9g7+-B)ZjsPOKD3 zLI0*By~7qUw!D!1qcMX|&{?Bc42QL-$T?CPkR1J*2#S`ZRplLbA~XuhT0eKf+?=rg z;Tzt^w00QR$?l(rd$~;2JyxD9FER`V+s?Ao`N?frdM}fpOpapR4sH;Q2$Ljow%XWP zm>}GrOJf6oec1SNmB@gQILSmyMpLX%s&>IX!4xmU0 zU8Wjt{l5CcjX^Ty#P}yd#^c^1nH$_NQJN_6NIiaf>`~z#c$_w=#eFopx>DCKULsS! zk@{RWVw7{x#-7ZGJP7l|5U}5TFUO`KRKhI;^b6t4VhWY4mlmLAf>=AVOo>M8f}N?YCU8 z&%F8`lJWfmRWASIyFh*iBy$XWmTOud7jQPalWN;gaxvU?LEz>!PUc$j&u&;hBNk>3 z+1k(=%qq$r)@2>H^*F~aEH!zdfJ+w*pdrzM*X6=lO~!pF(7k3on1hs$(5Iig=^*6P zyh8y)90wA#57%{zeaNJ@Ai31!2E(8+lS=hLLXT1|U79NO6O>FcTkj=3kQ@OnOntMh zWWz7)ivDpY)nPD8N$$N0yhY<<_jQ-A7bc3^J zxc+tC5o?G4MokVCt8Twd%CdCDvBV9|7n`l*j$vFB7lA3H2vPPPcol6o-wWLu z^!RXF^Etz4G(XuQpzSgb#_=pI0V`AH`kT1;+6g04GF;IEHMA6-cmd%1$trbhFm z!~!AMH4n9R3|3K|{U!lyKm}{Ot7MIae@&I1|0S)LOPwAY3q3QMiLXpng;iRz%EhnF ziOfT0->9M}X%UVaIkMF*OmZwjWBTw)ZZYialsW<<=y6|GCCKj1lIAYZ_EOc%e!93( zOyL{O>0N_7E$Fz~!op)r?1$=>!&2t(S3WUiEq*9>o+}+T$A*;d<%X}=UqXCk1PvXL z>hP$ee-rz7_NsvU9l^^VK+!x5rm|&y!6xA1W8ibb3U4L9rVK+-QgeB!Hby4^wBMl} zqB^dOeq$eK1>_M+>I0phdN3l8`L6WWug7O*wzWj)v6X(^nGB{DK~pb>ls8WpX zqGhcQOvj6;viz}`M>BRi{uGqU<%mvU6QYJw6fpO>d%tTv-D)pj%(sb%Ni^fa!XmkrMNaa zu2$Un1iGI1lN%9xp7`B1EH=c?`>8+C4^&j&m*c1cq8LJAKi>Uu!t}Y~yReW{wC|!b zO0_r9T}79kpRH9Tp2L;IxNdc#k*3GtE6y(@LP(YCl5} zrzr0uf>C6H2-=FmdT*>A`9_A_UYl95o4sH6@2h!1ee`y?F@=ry%-C~#I&N(6* z@mcXys;^AyqK#J4HfYpT$_pEyK-5C zPtp?BJ7+(?-?*bh-mYE@-0h7Nf9ZLdKeX~A`r%S|Lap(z@rI0O=3>w~S7j=a@3C{y zqhf5e?P7o?;yH`&6*?eE0npeJ$S9Cl1gS98%poFVOpu^sZv^RNX+I$5B08oa>8ptH zx!EO><9>N>u!h>G^(L4b``uknD9Os8QOz{^{eoQ2V$CnFL@4_7&wN*0Kq@lXuInN) zKYh2&BMrslEoR=;o9QYD{!d>jn)nER_i}C3V0ZLYb;dkW-hUo*jr}&X>rQ2OkUSR- zB!5cs-M_sISk3z0&$~~6(E3>8W+1zgB&LGPzIQ0K^agkFb@RRn1qoM|rT2qru;*ro zWF*Uw8(AQqdt6LxL%g~QT9S37NLF#hynwr0OWCwegCcNTtWtShjPDbp7XJAUkTfoF zAZN4Wba8{=btiRAaQmrk^wUX|k9_D?p8Ttq5p%+Yh+bzQU_wjUM zUd=5D<9>vby4TC8-}4v64%e?40Ibw%^`~;`ANm+SD&jjRLvOyu`tlvBuZNL_Q6{vL z%PqO>^FYOpkRb$pBGqLtmL3}Bb$MgS;BivP8J;+G)ZBURq6E~6D6YhKU-2U7dg2By z9z({dEgp#4G=`&JF07ZcSn^Da3P;)MNdy%Tzp)0fp;yrBd}h4BuD;x-bi1nM5>-(s z!B4nQUI}F}dh7m6jm54cIDY-r3ht!W02NKg3IdEH%TSB&hr_$KFicm(>kF~2eh;Trr!^zvAjfm$oEQ+Myn^4yrZA3_X4|B|0M}N^)Jv0Q&Dl(0X-9 zlw;p>m&-itOTlXy& zdC0MlK*Ouyp<7j-gLaV8P48FglXD=G)E?fWiU@QWdMd$x#*UBzWR^BHoFdU68tNSY zN3eHHKq~)-S<51C0$fD7H*UCOL$4HlfPiB5L4{YS;vIVbP$#Rp?!osis@{@5VY2s= z32j`)RZo?cepw_yQ?IPdP!6DS*yFAzKxbvq`NOXP&*x$r#K*NJsb0kiC~?|Ho5 zY3F?oUt3B4=$g>>ySIFDMPfY>n%gDid&lob_pk1K1nxsrJ)UE8ti1%6DWzCUN>BNc zQFc2?dpcd^7hIGmF+i+aen9S!ROU<7d15X8{fx^*$`Ig!DyPfKCNFWH^;Xb!&~_~f z^iK3q_pZdfT90vHm*r2W4T`xBR@_73-~8w@r=nNf=59DS{CT;V?}GyNXmp=m{*_L9 z{%}ECyjsKpkWHP^F0xW*oa(_$<2O`qguoOxwPt9So+roKf1Or*(69LO}i(=K^2Y?K1 zCim!XU!7mYiExPyKjb5^xsjsG82v%z7=_O*Y;I0U*1Grk7JkM`Q24+@B$7A2nV|NQ z`8scXlJ1(kJt0o>tD459I+s&y%v4dk;*?jbEmG&b+LyhM0;LJ0c1r8J&R3*v=9ss0 zguJO9Ur&nJ2bo|FWaO)1VkE^16Y`sYginfZh!_?2hs-T>E2VbKFZ)F~lzc!=1oc zcIODR5RMToy{*%NXDLdaN&-QyrU{@hx~hO`}<;o9g}~A^z6YWPbTD!RvH8?5c}&vK{(o$J)a15B!Rf zIWc6&SFr>OU7?9w`iL`QLpcrNj}nS;MQ6%#-kbV|vlS1E1ltUkNAD_X6+ct-Dp`0m zS}U)3>m6#nuzO_3PM1{CdsK+OA`cBcWi%1wzyD@crWxNc3Fz4|IriL#nkfJyMZ4v1 zhOTggDDXxY6(wsSLxubE{OB2=mNok}9G`R`W#%X(9ItJYcrYz)O6N|aeE3%Bz)kE6_16y9`HTe{Z>>T0_Hj~MEl=eOV>F+eE<4~!u>=%`5Pb0D_Q!fTbhYc zaqRXmOxB?$0l-&#jZP9fuvj%imvqLpcW}tsoHBDdsp4&=PLSGysf2R6bRUOaXY@L` zTZo{OJYO-|P24k&9Ocp>1^OdS%xh6d?~)y3&0m-Z(kVsJd;3}~)$&^}G8BXkcrmUs zkH2u1kcWscyTznfvb+-Pe|LQ5fBwCX!p$r*QB_H5V|Qsy_GIB;_qr({l#0kpAoHf4 zc^sajJ!z@Nmaxv{Xp>##2IVyCfNHjn>c`2;uWxsO_MvJDFFp7>B*`j`dZR~K?920{ zJPbF^un{V!adNOMHM#0_BB?a>3yv%zM8c$2hH#m8eKoP-wV z_a1wy7beuzyt|EN1mth3)DzsC^uK;dK(Q$=js(z^!i7zv@v_XX=@EoTQlTEi;>Zi+ zp&?YAz9QjSUwom=Kcl4C-?Ch$EqG`uM!0uPM)V7ksF=~ju2hIA=GNiuZ=t!giutz2 zzJH;UFToJ}Djfp)v1t0Xgg{z1^)<+6jCJ7CJaicGo+2-6a_1Hr2Im$1>Ki3eZ3;a% zKhs|x+^n^r<5C47!FAzZo&=v!L@lYub37 zyvuXXb+Mo~84MfL21)&ARCc^;zXLX!R4JScB3TH{mc$6t2H4Dhc3>ihT&efh8AfvZ zTS$@W&)BV>j}uxm(eSh6!GH5T?IDX8PH4X~c0cr3l0rg4N;eZex*p4T5Jq`_CZE1# z_!@F2oP2ACxM~W%#P>HL`C_SPTl|;2o&q{j$sh=dmCw>1 z1c_W)Zm%TV^*;zL_M03uWCK}>vh3-W=Nxf?3Dse(?wu)8P?miG<{6>HR>qWE8szb@ z^t7Vmh>uyBWCHtAa_5X=AE?ym8K*;2Lpa({Hc9JFA{cQsaeS7CV%IJxY_O~JspI#K zy`wd}$B&?#YOgZJ(}^I9KUy63zvtTXPPM&aosvIm;8)CGN;-dgvAI+%;ve~vX+wXF z%kHP-!8n9TE!R6Y!&B@B1a1XboG&Rk^wkeS{iZ)y+E%OR{ z1_8C+V=|m0X)cQrK=-cnMv=;ycK-VXnso6-N`ui+6`8XOIbINb*(HKJ zGwIHPJ8ofV3oEN!U=KXsKs;LR${3`+uJScC;uVeO*@&3=NVW(lh1Ml84Nl&!4X3uD zfv%(22(nl>ztQT8Q+tn>rT9TR9|WlJ<@YY~V^T&VyrF(Nlgs%rg!OYNx&m8or{#Cg zRcDlbcT`0kR!B|(KTy$zxyl1NZ9jcU3xZgGRn|x7z3JGiA3>Ca%+;mDO2<^l$q~6g zM!!wfGT9ri5keJVIKh9X(<{`S%0f{_(GB0ufHMY5Qd#%?kcy}&!jRo5x|M=3RA&b{ zG({Zy_a~M?*B60&j(G%lY^+1A-_TJOg=o@gWDWR=-l-9==@>`_N%&4wmzu)@!xJtC#Us+O2>l|S}>S4fr z&8~Okit9zE4pblh7ys3dgjcNRsI-SvX|b*N>`|lys+M|(XTIU^+xZv?du;TM2+( zdc-Y$=z34kdGR%=sM1)KqT#!mMFuQuhG)3eobyQedpeEC*&bcYecn>)`x&BsXEDQt zmxL@Kj1SAVTd57YA4sXO&^hh~`FnJID*j}ps(Id6xIb=IGC zTv7zFNB?p4ULX4%!q14cD@AjU`myK}e<;vts)%lAQNHn#VUtID^yTiu?qiWuL99Ir z+;@|6U8PT&(?6E zekz+~JdYT2!I~;s6_DskWqIM$cEkKz=?b6Im=EP*V-%`8VSEO15HrkqxUE~J)?o_4 z!eZg)uSD02pUT^dhA-yR(3EtXbQsc(H#kQcZGGq_;S_~` zZTLCLobcnLK3r($h>M%)f!z{b@U5*bKLWu~Gg!(k7bG9?R+yuExjIvCV|+GF6z^_S zbs2h|$5Jn?dl)9LYt_G9Qj<0A8J9>FHvZl1C zmC{dfc~hmOp*#y_5&CtzP!hc3Ei8{^fej|OATO_IX8T>H>l>-Nd&ZrI{^lOdi)`C^ zU|(C3Wg9IBRmWqJeoDFS6}xkgedXoC^|f`1SG&ADpNTgK>OFG`y4?FtA;K|~jSV;R zkHx7sVPiv5C=Hld#~F`9i(U}l6;hFieaf$c_Z<#74hRFP2qnd(4?iPkQGLdamPdzU zf`obl^FhY!8Y=5EvY)&(>*vJ|*iacers?lU;HCvst=(YHyRLj#D z;#5lVs-Xj>RNlfq-J;Am8HHyj5^GBlEM54aN4REsK|@4{Tz? z@T|{aFow?_e2Xb{ zaH339y*!*-IA^v@*{bbiNJyvAV&oF)9IsI6l`g7$jMjO)L8M$pi}n}%MTYGju}*Zu z(f+d)xw?8X1tONQ+6LPYUaz>KqrCN=N%8!2wUl#zJLvPACB0>Nt1_Oh$6_hwF7oJB z>p52s9Zj77$DCi6L$P5uHx`!3izg~IG6Fk@8gPwVtuT7@bSL=|jf@f*Mk}4gQTG7``%gxFf?cP=;3T)ir*D zEENBXV7d3UyZlFqmL-p$vfRiHNB0}p`Ni-aI7N1d8vD5O`BP3C7>Fz1D}`#ok27>f z_kMwZf+O)27=`xy;zV9%h43MB0EW$V{JUe6N<@7A(o7t~ymd9375;s0P8Pzz+-kDS zK96K?pjy)MpwnorNu2WIw!sLE$2H{@)~x);u;9_<;I>}bDZUe_$@{iDZ9M5n$*+r+ zmC{RlCOW3rdDbrTa=Aa4llv-OFp6Xu%51lY`-0$xUdz(T~duc}N5k?`W?&8OT}IN^gBc6sXKOArhJ86S|%dn_!+a<0|<>f5>T2NJm zT|Hw+O|`Gbri&#@F3Hgf=lW3Kme4L6*HMF-xb4DUPJ+N5Ve7GefA&Uw1Jh({?rQ!kS+cZ>OpFO7HQ z3uQ3x%mYjQ6h=1P~N*n_+26}8_Qecp1q5p7qGh7FOdcu3iL z``De75nOk4Q@`!yo^j{RBR(Q1_Eii~B-Y&Dx@%LLG;rzGZ~XmbB3n{BHD}q~ASm9{ z8<+8|5`Vw(9~G4cnmfr-ZPx2voX=_|S>|ub5XqbPD^LuMOTG zJ&>}n_R$xz+TqehQGZ1WFQPi8JEgY0O}$xHSL-Hi207_R2@hPJ?aEl@=*O>wl)z;x z4fXrG`?dq8*P9uAAxv#%t&L3aFunVhHP_8`{0*9?r<`b0!alHD{G@RU??iWqTD9;@ zhoXm*<3^3V`F2A%5x#^+XL}(>{q$;(-ImMMb(hI@k~;I&{`#N3AceYVpho%-mmKjB z`Nmfu8_Gmj&TIjX6(L`{)Cp&W=Jy?)XHRHuwWw6mh+LV>WRvoQiKhBt z8y-ba^Sj#tdpDi_WKqg*am>9f8@f}Ipxqgsa_8T3x@n6AN>olw1&7=pX`p$2hTdrA z3~BsO`uMR5m)RJq7-j>90ppDBtzVI6O}?ggx;LA=F@M6%Dz)t#ep`S2`J;Qk?4UJ< z<=aP7!ja5?nPD%w2mO07u^Oudlt8>Py{nXdxbhWAvrFAER4sxOfVNnvBy*~WoJ$$AWGI*#d{wgzYH%c<+e zCl{Oo(sV_VT$ZHlgUoUv^IPyc=1Cn zFj;I@xkJ6zJYjMC``m({kcJ0bQ^G}Oq1(w6i{15NIlj0#6K~_ewZH&*JoQn><1U=O zx+z^ckpbz6abDmS5&QKxHBcDmVqr6UtKdjeW#Hr1md3r=+r7*Hok zof@JV?5bmlw@ux~rY+Qf+?AwB z-kQrUHoVnxK@7CVgM=o4GC|7ZWu2wYGV&L`VfxuC$=#`^d2CQ-G)bXh9b81f>feum z&=u?Ek?%T}GBpv}L?VJ^W>$!GU(1m+91E|SM65w6K4;`F8I(^*bwJ}m>|E-|Ms~C! zW6Mw^?S3S&`*tq$3qs(D(URc4jtVUsWHXuEZ6g;puCJYOe+MBFVVpF#a_|5mQgVGF zAx7z`4ELzroy{b5(SVYTi^LAM&jLtty*BDs>GIf3@HMPiWAhTJ>t|OwRE*mwEs3hm=ACKPOWMVLKhX3^1qrL%aL)>>=kPnjw`uJoi5iW~ z;Qkw1%GZZ6k}@Pk0r0OT>MOs*q)&M!FQow2{xGet&rKzqrn?-t>`g0x`VCIW|I^+8 z0XQ&l0_z49VSsXr$XsZk} zPc#hnpE&*bM2LVTWnmI!*Xp^e+!BvC;_2NmrXXKMnO2P>_n_WKK@4^bz-$o-4|UMK zZt@|li0r?&m=Q2jwiBr`Zs!24hi`$mUc$~^J=&60UR~c?+80BWCUuhMVCQ$nWJ|Ts zhpgGE*R$TgMQd~??N94l+tT{AUP}03k`!Y0*kma_4T<^| z5=tI&7qvLxF9E!MUJ<+!?q`Ik3i>KuF`jn|*hvj-ljoDH1L_)6)P1?SZESu;js@&m zcmgusH z;wZ1q`cuPLewS{KW8Ur+eY00|(oY}KRKn82Q*>%3pqXOMrH5g-@KDl2un~%SBGp$w z6!9$-ZBBCphztxD8&&0P+gH)lVjH+#2g5&I*)v$)SeILe7H;_0v7dnU!dM*K>c$hJv>Q_$*AxfN z5^|lp*?O5+a}q%z85CxmD99t7{cGr5jNVhvxffC_wpKgl(7b4(S zk27Tcvatus7$5Mrq+gmd0YgQ5D<$1ryjhNDXbQDhs%JQ65`@3rXjdz%F|>k8tB-F{ z2b+Esd^Q7nNHof!0x6mMRl`KZ{fs01P^i77I-lO;l52FVGW~8n<&spNG4@6 zSzw}Xw9>$Di+wP+BZrc8;xJresb?Mu-?+d=r)=95uu|OnvX`diC6SK|_k+pZo2kL3 zaUTzEfgX)A8-rKJF3xv7v~XC02T8h?4p%G&E|o@@>&;@j_!F6nEVV%(N* zi>j&=@}-juyzVL%yknQgoDu8)6DR?a253Mhsrrm*0@$Map$WYN$r^Tp`YYN&(byD= zp|T52eRllK!<<*Ox-bdaQM&QWAc)=Tpj)eJ?|3 zjfXl2bE(AwM!oOF9^?!WB7Uq1$t@N7{VltlO)&LxAbZuMQocJ6i9IZvn8%3&vhcW7(%A8` zhsGsEx-CLFa{nbIoH6W~kt66wIYJ9(zTY7!?KiLv@f8?^tArCAbYw>of$+1!*qW|t zy*st_iHlx|wzAah-%mQa_sSE?ctE?}EG>z1NiZ=vqRD37@P*A;)KsKl8d{=nN^Ig( zu`-XGptqSQqs80^o`9e`O>p`0!Xf{f`BaKIDoK; z0KZVx@2u=#SwHqL`mKazn&iCqK-FWNrh{OVYeAe0 ztjo%veiOTWlfFL;us>?My=lW@Oi8^#Ccqv^rJ#g}p);RM#xp-EeK}zyDp)p9$@#D@ z=`$h=)`EYroC4d%8w`P4Gy#%b=wJlI^_@P($RfG-)L#i*eQPaot~Y9}%pBb|%h3j= z^+jRVB}bQImdu!RB6)G5Wx4v-K-~kn9jX~C77^Ef!k`u#*=70kmV2` zscE`<_1WH{)XB-o&rMdZ9H5Rbt2`oB%DmN$C!KS`=#HSJ%#Xr4LC?3Ss7T^$)rB!3 zd3E)i>cGTEcs222v4VZ7CB3-hXX)Kcw7FJXw{mhS?p${T~+@_8aKXhTwVA1{z? z4rXc!LJ4_Pn;f@EVPIfzFZ)FuRsCPGP3J;3W(j=-wYZ%c{rMRPorPYz#`3gW;u!#k zo@qExfrpld;t3<9xf!W7S#VD}8Qnd3_?-dDrjW|b=Qu&K^eb*DlJ1&S8Fl&krrZK) zPnq0da^icq@u_UXXb*(4xdQCV%;Y5I{Em3tR`=dqiJ08dQt6%D-Mp$1Y5ws$qz(Q7 zZ7Ob(^5MIJKtDUTFr?_IakFX>Y|PP|YszVr;h%a*jvTqB3a(Q%YWlQjGdkt;v$EDH z;h#>VuCK2r3z_aGNtC}L6B=!%xzzjHIcyG|qx6Vy^j4UK|5M8}IA(urm1x@eIsY;v zoUcHHjm~=iSa<`UW}0mRL&DV3gFN58K|HD$H;qln&d~h{D=yDRl&O7o zh&;%|9rs7QWt}8e&w^=awi_q zKE&1@A|ioUW~19*@k&PzqsYWSO-{R|Ll=rO|J0d31z?)g?**;F5+eY&H{4#4Y6q&C z6r;-}W8upv^25&^Z58j=WL;BZ?Bevaq*pZgcOoJzyE*dU{tP^Wz zaWk|tsjxL}pqjm8Y?bP2UQ^*=y)PI0x_3KLu5{=)n+B_0ntLpQv1^{JO<8SO^jVTL zfIFEw(F+R_6w| zM}run-q-#;B(d;(fv}hoTag#&J0+0R8BK|gVQUPWWTm?0(JguTNa-Z8uo-NTj{VHP zdlo$o8Pd3&Mt8(=Wl}hL=WN>*xoj9G z*;d2>bGsC@BWS2I&83!a5Xj1&1eG#vf~HP4`uqc>jAl;a6sxj+(tpyWm|&mKDv;n} z5?^s{g(vbn9^pA@?S0uKNjOuc9&b~c;D%LKzB*GomQyO=yrY6QlH0Z}G6|9T4FlI( z-~|hw75vN0{+|3{#x8X3>;UniAlhv>@(j#T0XbV%FPk#>2TzmqmtvWc1q5BS`nTeZ9(mj|8GY#!D@8!PmJJgsB)AAC^70f3^$*CThyI{bTG2ikTn-zR^ zp61qMIP?qC+AUE!sm}v$jhwNpXk80s9jrvT_TJqp6byjvby@PFvTeK0ueDuT1QiG_ zpPbm!eanNSL4V$Cpb7(?zu+F$&S$VG|0WQWgm>N=v|1XW7gtiR+;MExwrb3ZWa~n+D}T^*&lkWW-r8t#+-*Hpr)|pWQJ`ekM!?;oO>m4@ zA{e;w<6HWiSb(uJ-9p{eatvKk8`3!|WEwh#XYuG}c2cg=(QFFueZQVd{_d>)TmYY& z43eVWq)`1Co{_)Y8_QrF*1W`Dd8%zU)v}6;RX@>uSqhrgQkoJ^*spTQ%CpJo^j<0v zW1u?7!jEry_C&r>D0j>lZ2`BOzRHBnbcBwB4VHK|=K}J8U4yAJEyMxu%wVAQ`y`Dw z3B|NBl-W%iarfE0%;QR2kp>A^N^?Bxt2OhPme8H7&87k&mvElFSlA=>T)KHEq@=vf? zWPm+z{`u>35wP9(1b@bk7!grS7dKNvx-`$(Q5uu0bYfCk8g;#EX)GlaJHk?~7*Lb5 z%X`ke%e_C^EDf7T9j)byI45=dfQWxY#Ij^+2Ys;j=8kH_o}X zs>;8$Zmlan9@7rCr}dQJ4NzeFf<^)RG0wb9$jC<93LDebcJl4cLR*g&r(Vp#KB959 zIWhb{_Xfg`1ng7VAIfurjg(&^j5AwK8Yl$OXcD33s!@ZihslYF0rlQUYBo!%Ds&hB znxn?+HzWtCD{C&obF^_t;fL! zr4#!VK{Ic8b)(aq^)M$mty<3!kjo?`7zC%_|1S_mVYrhUI`_@7cP1gm=h^G3Cp}I3 zlDQ~Mxc|u4C9&3E%-H!KTd^@?`!nU=_Pcmqc&WE36)Ee|(!?yqgj70XP~z&A%ZEwX&on+$^0(9vL0sgqHh zLuJXJG&eIz)-ARu$Wb*wA~0Jd#rQZ{mm%42R#0vXGg@O9<=C6O9dP_^YHF&vCM9m> zG=4&=a8@{LHf8G11SiHv7E3dUu-?-&_=7ErgK$g`Q*1T^e%^g?m*PZ02A7h-;+M^y zN^7MlI7|9L*6Von>+_~hqtyDwF>79?{;4nO2`_1WA;`uha;E@+6a4r6Lqp)3F296W zn+u)puse@#=9QPr-ubJ0PgGT`{@V(Q7x=E)Dr~nfT>i)3B=4d2r6V&l+?3M2#XGRg z5R#TdgSK4@5>=Fy#f5|dupG@TSlnil^auC2dovVivKAyMlr0!fL^W$uXCv3Vlo{NX z61%ns*atCbsbl?T@c^T;I{Am86;^{R?S#O#c`Qn^TV5*NX!1l?j5+Ip?x(g$p_!$d!{J}mZGyLcN!dhpM zF2(Wk9)u4wWZkM28e~A*{&=8tNXohlo^pGcn6;4RFpMFZv(?1oQ!no4(bo~-p|iPy z3^l7Y7cH$;>qx3-yO9J*ljH&6KGn*6nu=a%wSEbtw_GpyCb8xoaym#@ z!bKUl>k$Q^4V6*meow7H-f6RE4m+jw_D(#)T(p8j5}yNMaRJEmi;1lq;3M7@q~3bvD-O;WFjs{N!~& z_|*e@s|oC#d5k9&M+$v&woO)|S{4ua0=8#3OQ>|)WHPqdnv2tVmYmqxGrh7zhP0pDnNnb zLB44p;{F@FN5l_o`?I@3jE6HjC{gPqX=yonru2OFlcV!<(X0*m3Nzi`f`RX>dBW3r)rv zF7o*G6(zxLwYxW*C%i^9D2zScLh_&W@+SdEgKHHae8S}i`ZRE2QaU1I(raUxweL(> z3Enno)S6|L>gj!CkhWzqMhLdA^5d_vA8B~V_#Db-DPS)JELd>R zB7jwtclUSy`-;TZm*TDWooki9^tBu1)p)PwZ}F)>%|`pYx{IiCkJw#7Y9u}^ENn@! znx5~Bv*k($Uyunc+>nTP)yE_M_e=@knUU@RP0!EV(iFv@l&%!Lq}Qw!Z%RbtBNGdc zN5Y@f+WnxSX3M<$A+R5=XugxBPaijj*V0|bv}RB#VNgdHhk%Ke788kYdz=SJKI$Jw zkbpOQinj7#KNM`sE1}tZt5?H$G;=&mhSIIWF1AN7okVKp4j)RYW! z*plUxdOSJVG|4hV!|*8HyPqw?{{t8bu+g-U3m@{Hh7o{a%goMj+MVnT#-i(|E<{pP zsq3Pb)V0nBT0ll2+tAWlP!V#+YU~=GA1r7!xg4gaysib#9R7ZtMHE1w+Yt-vudg9M z;sOIG3X|3u1{WAJt|#P4B4?nklXSc+o|W9Q?E)_isw3E)JbbhBzZmedB*Gbe2R5Cr z4h4$;AHE2!0}MRu@$Kdx8~*R9xQxIIfAK`_{PRox_dh`#fLYIovc3QJ*7IKiUjTms zbcCFH^Zd>;?*JjCVX#-%`nB+1(ET5kB$0oBs1Qrbiz2?~3IFeZ|9_g^TBU%Dj0|!^ zdVWJ4#E_mAkU1*$6uGbhM`vVOCA0vh@uSE14wN|>0XOPtOwuOD&_MC}B#kPKKdU_! z!OXW9v=W4W4Tim(K9JIA|9F21G%wX!18%e#d=H^HpAUS;8Z8HVO~SV1*S>H zfS+%L`u$?oGA}DjkjP_?If>O&mV+&b!zdD<3DyZ)6~p*sHsa8`D?!sou^ zaEq#0RPOzGY=1I)-{LyHUoOJY9YtKs&nHCz0hs^+NsaUJzoRy5ib|lddE;ifNA@Bg zCPFXq!_n75w|7ATq`-WF3hj`dg#oo0kmBjGo*=L^MSg%J^itH)np+`EciX_d2Cl2s z>=%QDUO`*F4iO7!d6qnZJ3yvZI9CzCsQ8g1ma12McsQ@{=~AZ6$uuw^RU!aJvD1&j zdeTFv;_?8>{-3n}rprX~`r+Gr7qqajFkn0aNQ@ccf8^b`GZH3A2G)j+aPS`ty=VXv z5Qs_s4Y7&K>#u|2?5A{Gx}4Z^GFMhe9WWu&+o|8B{)0IhY&e4wF$4eVuJ#|m@hL&m=F7|2gg8#)%kt)(On2{|*@pRjWueHCOR*rM&>5x#7 zvC{qA0+-jd(rTufBFlr&`|8ih-RTgOBLmKxAE&=r|LKxkSrR5D+h`iExN9r63=2_# zyusw?zw4)oIy^!Z1e;O2g`?{#2wX)w`BUdzm~T78P)0kV*prMv_$k8y$6#2+v40n0 zIQs{1O>pUm9FTp?un`vjCsGI*$TW7R(Zn8ng9?m{BlzT#JrKUoM+6J?E0msu`0m4rkEaQl+()eH*e}zc` z5$Tr}FQT$>-IonCjeTM^R5(Bu{I+0e!G{wh%UGXaAzC)4#Q@)&*QW?O-}6~8*@2HW zPOjGC0J!v^LYhMO(zxP|nrkS_q_@-WNGJP0nF~L#KlIHe6aDY=M}XrNk+DD8#W1c9 ziVV`%PlHAx0t{}Sm0%7&vW@hE3Ki)6A={TtdwOTAU^2R@~-)WwDho~AUUXr_@#yRlT6+e398mHA6x5U{BHU){X#&~Omn z-%U%@0Aioq(Mb68A){7ch|)s?Jn(suQ9cBuZ~tC{RT2A;Ui}&QbAZ4v6AtjcB<18$ z{t^J_J_tbfnPUSS#`>;7=*hB`0{%qYxsLP;Fo*SnIV|Zuo}mulu>aQyE~5h+rcE_1 zNP+}#n7P0P_A^f?fe|ZiHm3(8#`T0)YOvB+DJpgm?vVsb88ZcxEPmmQh zDb4f)dZX`-FBj$+GJ0UxjvW+>zZnb5WB_C@1BJ{n0Lbv!IiD1~A3Yedt~9AJ@C2l> z0mvvXbvLHcHknRuBe9<$s{}(gpbFcgr5YZH?dmCi3W? zSiZlQlXE}cS?8XdO_TP#08aDjry9Y2fdB~w0IuRs$t-5T)O5j#a6qG)#r*(IZq_E$ z^x|yW?^RXHG>yG1EG^6Ql+P0;7F#{~fOB`6rN@WMyv)o@l}k`ph;8FK_Vw-3BV!7O zbu?K7@969S&;vq|X0k$$3Ak21xTGJ51XI3+ECi~C+3%&Kxw$#1NMObP!QETNMfH8{!+?M&q97nrf*>uR4oC@% z(%sE~K}re?ohphTAxL))NH+{=f^?5GNDf1H^WTHw@AtW%|MTwo+%KLte*HS<>~r?s zYp=N0wbtprH|~946D#iHz55H~cuYW>v&kdHUl!KZ?_F8PT&IFF&ZoRQUfkQF#AO$! z{F9j}s|Ip)f8}LD0T4cA7x7>jkmUl=a-@M>ofgPuGtu}4vRHOaPrSMk<=7!4vkjnq zU7nwN55W5s8hWmMfAaP`gQx010gQ{ce)s9FE|0Z^@BCtEr!)T+K)J&JQqu{)J(dVi zZ|wp=1Y0$wnb-)yCr*Ccsu*|2aO>5%!@u#Xi0zqtE7rZ@ zd9;NpvFK*f*_OB>su>svo)3?}Kt|0~~WgwDPZx8&<(5nZcqWmYKVfjm8CE}eppSHyOqMm|UIoKK$| zV7Wd#N5?x2(L#>1&7z=MX<^IL(7)0_2368y`Nk$u8NAcpbcBjwf6N27l9@%}m~$dB z^ze`6MiHesytS(@=hq~Ae2zC5##=Y5Le>>-S$frSbnm;hJ`ooL>f>cERUZ}shBn@8 zB%(M^7;gzwhk}TZfFvUq!ST_GPf$VSloAUIi^FFEs%AUyjreO+tOMPg`$2Vt2~oH8 zeseB>xTNCRuYq!x1RvY#dzo(LAb)CEpMJ8`A%5biS81EbucbJCElY!HrT)P7q)pFT z6J_pD@HVr(zr2VqjC#efx2ucs1U$;?dzMNv&(Mj)n@ek=3`-Lmo#HFP`9T>NqS{-z?b#Aj93n;C6Hqn5;D<~<5WNzvKmo*T2&w9&c(+nEpbIZQA2jw*1; zu%bZ66FA-_p%>P49(f_7(+hu{NcjG@k}#9XIgj>C$*f6BHwN$B)ZAvk(bExjUYrM- zYZe0c=!g``5EfByvcr;aP~#k2rOK$Bj6s zHC<;u(#(fku`g5l7z>g|g;ZTt8ajAwf38~Ds%>jpAhF$U$Us4P4DBL*IC%vY z?b=zRd>i(5P;PnoCs6a)SiFF6C z-e(wb9o@+N^+SS6yKHK_hg4kzUxC_<3V6Q2y8v)9vL&}DjbF4>2tHJUfHI`9N2>pYI&Z35wa%bih|Xi&-dQ_RGx2|}WR z`sfbmriQ4MiecNH8W%g%UFQ-BU#aHObOuWTtZjs2ZQ}rc2qA$HutBRZNdhvWyhibp zJr%tg=lopn{h)oY{m)Rv1B;uBgT4A6M-G16B~hez{`I6F4c^zHz*{#hTmdw#0zx^_ zuqkdBkR{#`kuFw_d-tSDoF7*R7s7i!+ZKDzW7`^&jxhs(k`nmDx4GAk5c*+j`m(1; z?~H=b8AXrfYJ}6u*r;3fpbbnwp2`IU59gCj_X}T}Y6>EBU{#D^^xEIF+#YgOng6{$ zBtui%TONDy;>Em_5JG3?F*kF7dCAmD?a!jVH^ZHh68mIdMFEvt;l;kNL$2~*QrPEk zt;yhEKi|ugE%D7x&G=ErSn&Q4Fcm-FkkA^sl_eFkZn^KxM1vlLnY@;OY)Qu9C8l;qzu(l>9Sfbwt|86OUUC*Z=ys0cfziA;0=9*`ewyKAS7tsf zi05J~htW;)lnb#q^;`{lI3j#zZP&*`D+`=c0k>JM`S!pvXVbBW-%);!?$|XQ(=jaO$9T#1E#%e)+ z?_qfMPPfNW!^!bY@?WLA3cCk@*c?<99lLN%!;O`c`rgCV)vbNQLfzsoNbThFh+x}z z93{n>IDv=KhE^}3+(sgHW3^!yc%&ZKjk(9Dy-g+Nt-WJ1_WIY;Sd^ioQWej>XRPnz zU7DluRS@x!iLKOif)BiYaf3__66@UmPW!_$ZOy?C@aF=? z^490TpUhh}qInBdCvdv;c2BBLD=Vb2iwyEp8CkRq`1A~FdCC#;=E%j8*69_WlNx?~ ziy!Z)2b!7Zj^NyRAZt0MtRuENac+eJRewBq2%`7a`EEc#7FnSn7j#He zF_EDPMU{suGCoplWO_~(Dt=tWPa{7}XV-%S1~GrVZpBMQ?09$T8n1vKvsGX8g>9+b z3fE%huhTyxG6C--gqcadBE-Rp1IS+YuEO7ZTb)RR^XsZ7LIE6bSkK6Lzs0L}n5~({W-}W$5uAIGiOV=2 zmCq|`_cXp#CUZOn?MVBx1gt)iOm91zA@f z?!8xj*^DAQ!T!sIr_zc3cF&|Ib#Zf7{jT3{3*Zx)4wJ|ej#smuk>6{RE<}9dOLJjt=EgtFOsV>YdRa*3} zaB_0quG#1iQE|%WH}A0w66W3jo?eJ9Mt%d`Ck%NhR{*|aHfO3<3Fpk`$@cDgc>bN3 zc{|GzB7`39tMZfs)@k&#Twz%&4>!_KMgBBElMM$R<*kS6ijP3paH04G8J-Yt3bCI@ zgmGANJX^OHj9C`t;1HsCSHu4*H%}^N3~cKvQfQwT^edU=1lZQ{2ap)44jB~LCZZWd z;d}--nbpC^t^D-V<4uIPRGyBOj*fY-caNhma-oH*a1&P8q@_zNg7u!Yq9E}h?$wxr z8k61C$&RlBk49*+k<;M{OaliFrkjJ^m6XC%qXQqTg##DYTY0fAbpRJ1Bf`pSF#V%# z!;!PlQd#fS8(IkF+8b`uWGF8uig3d3aSFnL&3jqGUJ9edO*b$t-k_N3Coq_lV)v~E zZs)2T4XBksgj&l&_v-RH#SqdU!By^(zakgGs>i(QJZMUFx0m(n+@_u<_$=29)=${= zVL*nUD0K{<1Q+#!uL8^Kzu9ny?I7_l3Q|&{$pz-T&sln0 z!}(Jb0}@ZM8`9gj8__MR4?H%gS|4kL41wsn(|Ew;E{PvWFJ1=dBF=TFaI|6{ww0>#xd)bTV;;8U~cUro- z*Xu)LkyM~m-Wq*xe8cRMPBT98i>akSJJF5t+U?IE3~DGi1jQH8UM*X7Afm>8rGR7i=B?&2S}lBF1=o^)5{1`L_VFf`gb3>h)e2!*lYj zE=Dc@nSI;#baT=n-3EVcFi3k`GYJxshighIdm4P=Qh_+KQG~rUw`n7KhF)=lRQ3ZF zePfb$R;CIPjD3BMbz+)R3c-Pv4OLVaD-Tl!8Q5#_DL&d)uHQ-d?%91W#<)J8!>;3j zaEp}8jo6gX4f}7-4cy8X&H>Ypvjz2{bl@Uz)nXXxr=$Ri;q&IErIhrkn46&1`f zVscx?_`MC;BC4KA@4TjpuLwG7I5`maDE)3X$M?k|mI2^`D7-Bi!CoBAyq~p+{gerA zD|I_h>-dw)LzO>lDhQKw)3V#x?k5(A zZyg`3)C6{01{JfGZ!1SY?1V3=vp@TYeWMhr_8RRPaZGs0K`z)*uIv(4KqZv z;M+XH1$H{UQizDe+H^0c;>9RwT_H?Yy37^yxbe7I13y32ui{RbgMkmyG_nq?=3zhG z4!=_wJ8|#kTgCBHInhGQ-j|bBZMd(@a~veN93cQ3YY=SMQg4ABNuOZEcDLX88}kqP zepeKrW)$~-wCG+lKG{e7UFJ)*13%e{Qv>0S%Si=;DV#nO(=rQ@GfbigQ zLwUcV{#|?QZxU<4!r+P}lIv*NmETHcN@=SsfcLfPTuVy>?GZQ@iqH#cae25EHRe5c zTcWbAcQpw(DR$Sm*bjids|mp{%_EB{jolBHDq(N=U8(GPa1an*1j?gzD=LRLn*q4g zV_)TlRZykTWf$2}8Szx*pczD`)~*j*^PJC{Df15;{>3vv0!35X`C51^Pg}dS?&L~1 zEPotQ-&A@$RypH!yf>GIVNgmCRNGv#GwWW-(D_1(Z?P1o-mcXJ9DIQvN-=kC7KCna z2hsd8tadPUth8FIazVxy6i$;|)Y8_*y1x`Wtt(3|lM%!BYjN3aD(g8qV)=f}c-hu% z6>nJ40(OASivGA+w-d@cPh_kv!r1z z3q=qbZ8}c+Q?=OEEU}93{ZyF-j-2(DEHBA#*K;@D5eg)1ZP^048z5A`>F?W_J1()6 zlv?&?nIXQGYbF((gQGK}ktOIdL74rpGSY7Bp7~0kas#G6SI2eSP69EBZlbpDwRD{& zahqHOAs3ch0NrCLjGtX23_mQ`5Xtg708#H76}JO1PbLytAZk5bAFFIX6J=LbHG@9u zAmrcyz!*AwR?Hh59B)oK2^_yD{5A}lxny3mL^Bga_>513IU0mB7LI@N=*Zxszw{%e zKN$CreD-C5)Z1O+V-8ooDS02`4E9W~`$a1rT+1EGO zSDC7JE85BLnFyM93whlQWZ%8mEyu?=H1zG6tcY3iAIY)Hf))JTMq`2P+$!~okygO= zJ-eRFtf6B&|N83JD?vP~qX?DcfCt?4t32QG_u!oRB@xE0$ivg}&9L%{@^NI)6RrzR!n~>Gcc&z>olCmtjjpqU5-9_Csm&=Rn2Ht{G5q zcBiE82se}@ZpqI$Ba`YhD{g)$p~j62ph2B#fDA(r7q`5te)uoBBC~pGO|_wL{n80GgGiR=_cfZwYsay(uxXLsX}&kQDzM1K zIvSBIo#(T;*2KW3X|<9zm2(|8^H_ZM^A*D_M`bU^MA@m30R<{%m#k{Xxxf%k4*=bL zeG4~94!IY^=5|h1zo33=>1b!%g>tH{0h2M?KFBro6wa9{zz?aX5+cPPDbA!r= z!koIL$aK*2h|jOi%g~`L-Nw{--W}pGsM*ur=t3*d1UO{rm6{2;JR93q_IF<@M?I~- zpY$HtRRO_@eZ7@|&_>3yVLO^J;E^u7lRvo)`dbWWjk^mx?_)Bxxd>3Y^x9 zFF0uYZc_>ZloD20iZZ*;$ES;taFg-ni?U%No=XBEQnOSMk9ofx8d4OL$>rq*TNbA6dG1F*(}H`MaQ4O zGQvOBGOHaH9D&3E70O_%7DHzl+nc4d!G-K^s%1_Ba7~zMY{PzkA=7{#a&I>N87D@? zXFmdq#^0S;>P~-a;cbrG8Zcl5qX%lg9-S30`O3BU)ea5TK&L0x^oi4o`<%O61yzp#`orpTIK?o7#qMUMF90vi zWq@|m>4?!wBc@)hGuCO8&Fkw}LCx~Q1G-`_;M3QHpoy-MB+O^$&Gv9tjxfEYbCCU3 zmN~nQc|ihoqjDaYh!NlhM2o)9r|!`nh$(o5y^1p@Mi%;WrOuIde4KX|qqz<$noAR; z-T_d(av5+1szZiFkV#?tdrIYH8@pb~E@N(KpD9GpPV!44D*?<0wHpRh`lvZ*u#@}n z@9#m02!nEZ8yZ9GO0`(vI8Qf!*tY%VQg+}Dv0Ck*HtF9=aH2>!USZSkvGf6HqdczG zcWg=Q``Qf^%4pB+A!L5X0aYA5EWDv%Pm#}O_bJ~^BjMksSrA68cCEvsM~e4PsFP5j%~@8*uNHd20Q@H*JOxp7T&`~U`=fPzD_z$Cl+ zsLO;~gH}0XIf8acHxs#Xi&=w(EEj+7U%}1Rw9sJ1revXC@b;tcY(QG}6MT;ME<}Iz zC%Of=6w#;V9O{T?_Su9>_GuQx-=|_weCq1n0FGK2W0gT-iZaY%fom} zz{&VvR%i z$$lfH@6(G(WH(RQIru8R9IBr&5r&`0I*oa@?%PN)ZsF~-3d6|dQQX2HQ$}8&dZ9wM zci%}kYKP<*H>EIWe267pe}B*SoFf;048RTl$OcemS8s>1Dj6Qh+%^j)l&RV4_w%#|ZL*A3+#GX5l5uOV$(6%&jNGKK9s`P{Bt7VLJefL~2GLOq zCl5~)2&rWi8@~wCui(v_l|{sKn~^PT)kJUT0$4oH(q|}C*o!Onr=2M4Qgo82N5#T@ z;Tcsb+MO)dtZ|{je(yLC7JQ3eOK__lW?khpn@&_r7K347?G^O>ah17J)Icx`PXB;t z;}OUp;Np9N#5d0PB|1sQe>5=%-Nws|?TI2fPQfgY5}CHX!fd8zO(EspZuiq)>(e*( z_`an6@U$*f<($G;Mng6O@F>y>^BRCL{tEB?o>6BfzhQ(KL6sLe1+&7=x5jVQM;ki4 zi&4`|^Sdq*OZISmK0Q?bbJo`@;t%K&N;-$fX;En>_xZuTvj zGNX(C<>Ew4DIIJI`_+ZwmroE$?$38MYsK7CW1TI_b@KaCs~O{HZa8&49N0+1+_3D* zt*)KC$WoO0P-3N1jAWIC*^P>Mr7QJS`E{Q((|C?J?V>KlpQU5BbBJ>eSojB0vc$Lm z3%QOjx^rd7i9unmGO=ck=bur5_s7M@uQ>2-MY12fBp}Y)e(n!}7DPhSx7*s&eYOMC;wAY|BuH3{xfOwMfIFKK*?!cSk8R$m+8?(2$H%(IEeSo>pi``ZTkyuE^k5JF<`%F&gKI1S4JDG*!uFIj zLYj=jtonO{$;AP1xxz7We_A+Tuft_e!VUxKC_&Z}kY%P7Gl4D5%C%(w`=|^oQ#>xE z`jjyO^#rlY|JFEy=RnmqV`R&}tjXV**?`%9BI&AT!M-81^$&CA`5vAFW0)jZrdv;NczPO5 zD`SZqXWdw^3(bi09df$zq_O}T@LP;Z$f4*o{!^*{X94+u#L1KMRi27qfC`9I|F_05 z$6*Yk%uv_k24~>EW|l$%W?zmgTzOh)ZlTvmjYH)Bh}#%?89$sITq>y;_>Ud>9tgsX zbEksPh^x?A1@U@#g*`MDY0yQ#<0@TpZ@>a1yY=V|D|!jal$WosmYlO4#9uVtjrJkgmxt2 zZ%#F9859fA^@lsn$ZAaHpdIp(zki+$kcAefwFZ{)Z=Xx`fCyY zIDb+$L%v&*e(LiS20DI&Y9BrOw~_iH@g3 z6bSAJ#!>$iegblaCcnen!WLVTe=j+BR->Jwc60Ewf*l7pv=QKF=Q}^_x&U5CGP!49 zaxm9>oKA*N+%usM69Ws3E}d0AUEmvjFy}$35a9*(&hB3>3i9&PJqoO^1scN!;%Kh zW&?fe)Yy{yPEcHvxh4 zjAKk%+4!sY*$nWfEY?6}`q16|94VH2-?+eY3a{^-`Zr)~zg_U0?{iYNr{{*^A{IOd zPi8oh=>_5ep}zANdeQ1(cQq$CYq_E-HGgkoq{r(%iAoNkvHwHQ1g7deHALDQ1b6}% zHBHTL^9VDzV7ESyh=Gd^qG!9>RSTA4)<*DDp94eDEuDG@5_ypznN;yGzsj@#EcJ}M zO7B^l3Rx`g(J9DL{Pg9r@zSM(!y}Z1IhaC2ypbTfVGeP`@iW2lzzlj73*tE;L`d};?c4bLk=5(oNc=K&cT5Ca}X-aDJle|5va@x!_J{`%cV%0e?A z?b|OBW2Io=G4ubVBTE7Ktp0^5qXrVujN^x8_D!m(0vGJlCF*;(&OKM6ER_OA*uXv9 z`H!{pg{p#;Cdg#VJ|)ZgDm?yG^u%Gv5~24n8wAK-;34$*>R9df{7K7M;f5hOds{hS zuhHrB+sKM!QZ4TIzJ?;f+3(@*I64&#p#9?s))lAth_s%ICAJ}S*eT*{>;Du)<5Ff9 z7Dj`}V_+Za=tl7x^F&5Q;^uSrgEWdF)jaV106|=bRVnTkma(Yowm`zZfb?E%f%bG>;`YJ z6{U>6c*u!ez+m{=yUe%ibWrUQwDw8R{;d5;6$5p!!76C#RP`ImQ|FgZ|4B8MN_gmw3=-=PePTlf079wNv;L zt11OSG0opH2|U(2fc0Y^3`zyDod!rLaOaZcI>d8ds*C?&g@fxqUj@&|J5IrxJRrQF z5OdPYYnUEe5l$57yU}@TE?Mhg4#U>ze1Q1;ce|8E7C?BR9;-wdTwr*74%}fO@%ITs zMe-wO)oggTf%a-ll<5B}*Vu(Q8_gP9Nlwo+ z@hTs9)YXb@^v4XT8|m|iT#03@2_4b6oMa#Kkuv(b6O7gTxkX;|)Ums3AFBoFCvyZ= zL*P&knO7w|*mFX6=a~0d4gH?}@rh)8COmf1;L5Vlt=e|NGu_||wZ+mXmyQuzK+d5l z`1iH!!d)8NTlXJq{_t`@7wfd#NZ-DsNuL;*^C>4>N8Cs5IcVgt>04Z~F#$_JL{yCa z>SO2Pj51)LH{EPIuhQ}^LM5_yTS#Ik+DUfEi~XM)0Vbz;0jRf6CLGw37HEnV9+yB~ z*!akSLiKDhm!!-9{#~WLY0K(KbVGA~=lCrK&kft5PrAdZTvuve6do>rR=7oRF>|SY zHS$x0TGt|s@?2XlY|#NeZTq`b2+tJUPf-rfTYR@k_1i-Dg-e|2i zH5GnTp;9EC>y|>U#JM93UB9tRK705pCOQpAv%&V9iAjUnBx?wDQTk$O@3F)0D=Ht1 zN8urDd&}{w)jqYZxpZ|0YeF@?9@k&~m!v?6!8f(KMkQvzll?}>=Lf5!N4%Fu9oCHy zH8dyRJVL9kRXHbGze+^D5!Nd)d!fHFl3JE-=zZ8<;IZIT%aTh~9a4Pim|&G7*!wR0 z>$sFhGJ`I|Bd^e{k8||<7(M@{;e)(nwZrP)z^cNvZS4C0{g3H`mq@g_oU&o`NOXk zc&BS`MqI{a&DJ2ih;rDI7)Fe88`X8`UwIyVlN=UoU|elv8}ATN`*cipE}Aa|-eM&F zq4dhWzeRNiUvC~SycdGfd&^g})zfbWx~(^C9FNCp<1}@MZ1qx22RC@KBTkl@%<4V~ zZBMT5SQ*zc?HBi6W{p|@mf1O2BX>05`xb&z{-RY{XRpv_?bD%GOK^#EFAeI2hYZGf zOU{QZaR_DDBh`O$n4FvW<=1|upD5u^Ew&?}=hWUPbg{VDnupu>yd_hF+*jvFW*3ox zvGRI@UWC3c;cW)D16;TH_5;(1CYjFhlc9zT54X?&=m?4VA&hW`=96At25w%1sLQKb z!O;fZR)y9XJP7}0k(|u0=&Jhh<8_kirM>vCewrw`($dy2Ifua)4OC0y|9S(3wc{y% zZk-*=w84t+8{&DSjBw!3Sf^X>AV85&3$49jjwcbbG$N6Ha5&H~gJdgwVMJ3r+VzVu zs4XD7m0>@hYd3@XZA>>0;Nz+rP`-c(UzzU{r>C4&)E7`fdJotwA4MAOt!B1^?oP=e zUxao#l3Si8=XeMUqi4;JIjP27yh}f`aZ4Z=k{p{<6d#)fF8@g2W1PMuhL%lhNN|p@ z(lofYzd3d?%HmSL>oPtO0n;PtLT9tjHHjg{g@2)lQCL!H6CFsorU)~tSM!UVl4a8 zb?MIHJP+D=JiipLQ9%}D&hd@$<#pO)y7u0l{BYMuR{rk5^RXcP-H$7=r1g0UZwn@u zOl%+6;n^ysvg&*gW>MB?J{&jSsN@|ROU+1MgxHD?rp_^1(Tv4IjLBgT`3S;cTeofZ z>ZJr9{ex{EqkX5q2V0+~wn3L&=8vqGkCL2L`o87~luH^NZ@O9S8|uA?ov+sIeQP%K z`^00f!hSo%jB`jyyav|N+VZu@DZSilf2)UgX6(eqPG_aVYdT;wB=M5pqMuefjzj%b-aS5|)Z~-eofs~hOuv|o z%XN&N$(q*leMp~Lr`%i%SW~v+0nb3OQKE-IZ#A$?FUW}ozbc9x=IHT$@)A5L3_TPV zT8f&=-%8n>D05@b!wvHu-8XQn=(D|xn>PG)4=#atm{hW(V%XI2VAarVGQu2d(bIO` zv!H1dbwnLv`Dv6ksHUlOnUfE*lFBPo5bX!Qv@(t9kFB@_GtR?DSz#xZ{(7aD4gX~Q zJ-+KM-DC}s%1a_|3q3Xy&%^whHFm@#yxo5JPi_otA2|!Fdardtz6MQ=uWlMo=d>CX zB_&IlA+tC1`f*U1O}1BLmu&I05KE44%C%!fUve7?(*9fJYuHu(qMRu=$h|t>6hA#Z zI+9K>l=i)xBsE|-isEjr0G|K3{A=@SW4&5$9G93yQ;CJgcT1PjpBu)ucNCAG6m#Ls z2rRXEw4)D_IB4*TBb3P%%;WY4iCMGP{NW0)c~5`pa>)4mL;d;d?U`)w7n`}tho!mC z&&QJq@2@Ec_smaRc>D8}-58ZvKIZu#E;~s_{&+2qr?O0h+bb1!b%fN7%DH!wp4>L` z!txDAJ5uU;rCu3Bb|FZ`dI3*?aRq~6M2Bx?$m@g2Uv2C2i$_mzFzJsaJcbiTRF3~u7I#_J{BEBLwYm65s5|su2Jv#{c;82-! z+D!&O>!?M-3^i}VM4XAwYaeEzgbD{=Oei~S`^^bm$@Dtt?=-euOWK!41}Tv?+2N~| z1@R01R$M5ao6nbVT8M|xmo{DK7_F)li1+QahG?xZ@E(mSjv7$Z8zB>$6d5B0h;6yhh?;NQkCQ5iD& zb6)8;Pb{Asv?FuZQ%XrMg7$wWS^_eQ!m<6OL@ z=@{c%<3u=j+R6YgM57}rzP#lAPn~gZbW2uH+^`(gHJ2>E{7WBe`7!YBPzb&7n#_Qc zz+ACz#T2@Cv`tvSMXBy9r}ir`mwMM+L#JuGuScKKKi=?|OI@{**D|L+bzyQ6DtSG-zNFebzzL&kAV^u<=n^~yZ^D7Q4`PYH4eqJ_C6 zos(pKriI&(-^w!Z|MZ=)##X_$*C`B_$T!|NG7$(=Q?0-sAH1ECF10{g8EVv1vAlLN zSaU^?Bhbe0t2w#CRZ)#cY4}{x(nlm5jKv|J6Aq$?YQqi zwIt@^gL$)NpnnW| zW$muAueadv)P=3LZItw+AeaWf-xkioA^iA2%S`pa$lQ?GCx2I?{$$Ev&ul<<@Hz7B zkm*PsZ$9-J4Xw{<2{|@)AuR;R#_z444#qE7CX$a##_9Bc1g)&GrksO{o0pi3i1)N+ zMv=E)>M2lij=eJ{qOvM?zJ0*2n*SUtS?fl!=oEhPC+$4eS9abCe2jBE92^|WTAd}A zLqu(kLpv&BPma|m6yY5YAKsp?g6+V)WN++Ec+btxiPJJR@!2pRKV$S?h!tC#{JK76 z9FdP1wO`J6_>Qhiw4y3cDaFf*D{vYV8XQPL^7=Sf;lx+8wZpuP)xy_R43+AInMBgE!MK*Qho;~jT5bHM=vT{CnY>OPk9k0-M z3a6SH>8P|PxZ8aZIesgI8Dy1^Z*sa=p;=(>8|gJ8Rxj_*xvUlS7mmj9t0HGS$GLoi zLx92#kHA!XaEDq+Pu07jX^C|`#8(E|G`TmAxFS02l!f%s;SKXa-M%xmnMa!3IX74s z%N(8T9BCe3U_k|O>0YGYq1YKBz{G~`|IDJN9ewL>QmtJ+l;oau%rC_wk{_*^quSqg zxLucEij0nAefC4cjg;&7m2RiLAhYp6N1t1S$!e9(#k~BZCkS~F$g7b;;w-%;3xQ_Z z^E>yaI7l4KMp~O&iju~Cly+oEGHN}VWOMY&wf*2omyzMEhkNZNNAruX^|Rs|yAZu% z;RS3)zv-?8ntxi7ms5^R81bT3geaw`8#{Lw$8E;A_haIz(fYbD+)x|m1h$&x<&GVL zhq;#`f)O*LD{u9uKo2a}I*2cF-}7Kt(9Q0*h_=1L&ZtF6e%ZXaE~IB5J_V&kUs!7P zt<-sOd}m`^V@V4$I$GXW`s%bN0@jEyDr55Wte? z;r61<@gcv)E227KLv9_H+RqDDXMDm>Cc5psA;01#6<=Mnx?gy|G)3tiq+n@Pock80 zeSvSMuhhBqIeNQoFWlr<<-CM(om-@#PX~9>voF=^`8|GV5AU|Gcc+a~6}qF^cv51Pe+%ZOw2mX>RUW3w)OI zI;wL>nupHJ(K&+uOmO@>K(Y_qJr`hXRkCa}&QR3rD3-8Zzg2=HiMx(|vxE`tZxEQY z!61kH?H1cQmF#WG%1@da5Y3Zj+drbVE|-fnmA2pZBvvZw=b5WqGW_&;Y{}jAZ>j

|ZF-7E&$vFa#1g{4xMBiTe9zSOAMx9K*akPyr>nrnB65IK;(H^>A zOWnq2VA@jqZhhOuF7WbV-e*L5`{QXZo;G4v{Jd;@TKJpNppcaXA&pX_#n|1k ziIh@amAfKdu($TxryUAMRwjCz$OvKP9TSy=$50XJHz=1lb67MxH(y7w>FKHJn}6R7 zOw&{GkF+xnjVLtpTo#gS3P#o_7oF25I5zN3JGbSf4T-~WDN&)(@j<43k4usl515Htq9#PO3Ha`)=db%Y ztm5*wEyA0c*H~Rs^zKTLuqHk4@R*E4nKYvcV|{z6k_tRXtoXu{5QaAsmBM5p&hs+2yMlunjtglQ)=bv{0ExoCqI~b8Dq?pELAm2zb~eN51TxM zlGs~jMJ2*bSLW!?oWlSa+(dcVSE~Ce9$M|WQG2N2+LNmgTCfqM)PCp11{oc72#;S} z&z8!^L`Rz=`-#f9ZVy*1cSN?H)U;^q_(iHuzrrlIR}`xY?jXYtFgj z8A8}kkp9+D4%c}?;l9BNs}-JBruMb~L~`k;EQ6{7N<5DJrMvnrP>-i*NI9$tCFyxv z6A7HHAxCx@UxElXP|cRT!!1fbKQ*J?0b=#h4QAVJ1*COrPk`X z(DTUO3|!kSz~dxHdqN9dEWo!T$@NI6Qnv6is%2W7t@$ZrYV=J$#^mevmf64;c{A2V z>7Ep6385@YS}W~F{3nz5|9JrcAl46L7pIbGEk8cm>Xa>rKq0gnp`K;)Cs{s_TDT#@GY=O{OFKDlt51M#Dk* z$xqR5|54A*4l`jD>i>Kzb?jTQbi&E*;F(4yYHFhum#*WF$P&Mvs(m3TK{YOfn zjIw@xjGn7nztq{q>7X48Zkt|8iaU;8XX`KPg@aqp$1t>tvF z{$KKwAy`kP`|C8gi}4trQ>S2Yq;L8CXvaNb?Dl66jEu`IuY8iN48r{Fv>3QWh` zqg?EB6jE$Qks;q&f?E?!u`aZqEOAvs#K^eT2U{xC8N*;6kY6YhJN>n>38N_JaD@&1 zyBr@S=cv;Y$izKytnA>UBLyImGoUpM+vLMZj7lHNuh7MVA)Q{_l2*RKV^xvo{O8;< zv80Sk?I)U0?YKnN1-qQhtPwQJY03Y54;!GE;_aswfPxe4T&LPdgp4SUSTb*mzMLbN z=?DBx5!r^I#k(n+B7r!XOkQ|ZI)8?cX9b)&6WavrgFn85>g&f{qTy?<9>|k|Om7R= zF_O2CJpuXJ7Ii2j`Og?><7)dbsegRRi{y+rS}~Uou#3S?S_PW5%z!2hj{0fhhXt;w zHMK{h@g;)b=n=3 z)XfjevUvVFkt9=`Xq-m;o4Zs!ybS1*t7=ftA=yQPX-s_QOp3n)@BWjk5gj%aFDc0= zB&0`pnT{ParO1J>YD&_4kpkajnIv#(V>{yMS-Ev&oA&n%;oyBH0-nVCt4-K{P-9Ag z!FZ}B@TftZ>K0%&XXP2y={qlvP_uAnP=iIb!;9q7i2{`-@?cx@KmUL?zWCu8Scw3i zQ$9^`kzxS&tT@RMY`z_5i8g#%_elS4|d zOX`I;tt2xJNtOC@P+l8OfTD+@g+)}MaWf|C(b z`6Gu$UQG<2Wq2vwRHf9$neM{He~LYhmqc&J{n6G?Is8T7p&OQ@SYH_X&s8n*;3_>i zNBTd{VPXUoed5i1IU1rm1MjmzYv%6K;D7!;cHKY2@#qQH{H6Q#076GMW;Q4A7cNd;adJ z;rTl%BL&b}k5Xm-cIT6fg{lpzI6J-)?GQ`)ca*@Sxb-~YPsS2TL5yvH`q2Eo zWmexNx;K5-*5V(})))E!ylCKCM6>9hji0#6ZmUiH>|yn)?tj!6Q4o01q9S!{D>on> zAINt1F@-bO<^u!Y`Ja8Nnxnwr(Va;!ptN8)yH}5yaZYt4^uYxR0T7S7{Ew~rf+eKU zed_ze&x7LQgPt^O4afUd?+Wr_H9#2xdv|rhKN<@VLp3mJ;MJ$%fSJP`_K{*Ul@2rs zd_u}@xg9Y|@jnKC1QZHqUs~TE!32YY#wxk$=68a@*LBP~5~%$K9OeGAS+3aD!z(H= z2Y(97iTr?E>#J*MfX-6i-C5Y!g6hK+pQqKo-!m-8LY&2GFk49&vZej6UD*n z=C}j;wcycKcS7DD& zRt6-^CyvPiF5rrEWdy^`B`Mh;oUFDVrT=4>B=d1#t7+TaUBHrHmrI7)mhL($J7Me+&a@g0BdU6rrZQ8Qoc{ zNuVC|!JD1>RcG@2AIZiQ2fukU=KU3HJ3JHU_hIrG@3SlYevyL*cyzwM_Sf?KdpWRP zcUi!;WZj?&{?qC=Wf$B{IsP7Ub~Bh0_^>W#SyJDhLju(#zC+}uDSm_IpN9FC52je< zRW@?{&!i9VN2pa}{!AY{0lfSL@PZ#y=l&0OU;P$k7qv@@h$t3FqtdB#Gl)osq%=ym zv^0nqARyf--QB4#-8FP5&Cm@4%-I8?@B3ZfIe)6n0c9S(`!vtsc%L44QA+qG zLHm_3Zi_XLdOk#_w~ps?{Npe_3ju>Mb|xzMWBGsUKhmIyfN@wK%2!fdEdB(TYbD1^ zWdEy+>3(_-zAT<$D|7Mxhm;V4n*Yl{6I0Lxx59Rnx&cy~6}hQlTd9?oh0DmfhlBcN z!K)WDp!^EFSnS<`O6AL4r|<>tf@t55M7oPlycYp$@+L!p=2C7vA%9%3^9g(sMs-B- zy`FL3)sr1r#dz^cbmW9K@hXw$-=A4r{6v8yyAa(ONJ8E(gI#zJ0dK&gP(U`gI^Pvf zNG2FVm~UXnUt|6EDgW~*09*3<1hxON!7GH~2GZYY6q);-@4KqvDctHE_KW+^NWA2M ze{(IB+Wx${g#yn9ZT)%%tvQ*@p8{AsaTG9q?9H#0q8HyX2XFcB;h}&$JWOqwF8=>7)y{$^9~Xr;i@ z4Klk*@jlU@>hMoqKX?am#i?LR`~{b-UoJpu;3n{mHgeQrCHpIIU`jcs*`841yl9?`og9 zB5(%cG>Y-VC2V)Jdx{Hl5M0lhz(*Wc$25li{0n zV(m?&o~F}vwMTq=aFIREka`WFn=Tv*Jw0`GbrE|9{KK;@Q>RC^EIS7?&{mCRs-Ew) z@BK6$SrJC?;~d;(w)C%=53VS&oK#1)Bd(ErSo7?P(tC20h(9@}zf1m0i=u5J(NI|V zaemfoh7vh+;+t!CMHE;j>K{9?up~fdyV9s7zBY&(qg}eiJS9^Cf!|!Fgbzu!Z@uM;S`4A^{``DKI`&a=!1tstpK9J$dh*CsjxYik);#vUZ{*H7D zd`Me=f&Ico?i&YG?N-#@(u zo11+2vzPskQVW9*P_=q5{K@oOt`v|Oae%<`4doyH?L2(}f=&O(nE78yln62Pav8bo z)coW@0dPbsL3={|jj7t#H(A%+rz>sKZOK7n*{?T9d0)cT^;YL)<9ORZEVD+^b6#Kf zTh5Y@GrcCd^4r$DL%YYI5(+vOw*gGKx;}%$>d1pTG_u#<#$HK^2jFK<_{d)`)DvQY zvGh)rNTB;-q!f}E$NOsxp#KPqvKcez|I4$I@l8@0i{|exQ~b%coU0R_9vbLU83xO* zulpw#x|-fX?osp4qV2;!i;RNi0~Ui``lB20qZz$YUXyk;0@u$JwdWQ?8d#2yvl{>hZ+&Fv5bKN`!9o9tpAt^u#TzV{8v^N?!Gt^DXw9Xxo$Q- zEKX#HFN3f+@H=z>iH*4M@W*eTD$6P6~j3 zAE}G%D+=EJtxU+jy;lRKBGN1K9HIpT{u;6^@ljDCz|6ApfFB=?GUojODNuaX1rNTx z>SbGf{^ZY8y%pAal`C1=&XpsSi#*frXRYzuLz*rs%+OMCVrxZyT(d;GY!jn^K*^3H82T)?fp4ScPN~JWSABHypzBqpMtNR|#W<1~_H}K4e}8guVac`M zrXg7c40KSChzJ=o%@G`ew%T(bcNHS)hd)2vhL3#Hbu^+G?E+n|Jt7{P6m~PH^c7qp zN&+2M>-%}nFWo^<8Sn-5UcOv78~}hSZnt)Ig~2!KbpSj}a+5AEo{`HNvWjhtqFL{^ z%O_DE^Y;qtF))*u-~M?576h3c#W_X{ z#Fi8w!ulPQ;4!0;zR_%P1(hBNWJkL%-pO%Z-2k>E4UOm!x^!^uEUKY1iY*R**>e_2KS806 zagp9N{>z!@wR9Wkq*jkN6uE+AUZ7bRAB=aL6gm7)_^1bt!SHH&^)WHN-}(+@4>*s* zJBb`%*F42^7rW!Ss|2Qei@na1oA1!pHz*Grl`&%exzq>>U!=Ra^&{yow)&4X{+~%C zq$>Z}$rniIjxYHQnZL&Jk7M{t7%VD3zv%kEwDj`81BWA;{EL$}|4LT6kagbtUb&es!-|VXCV6PO5)W5 zii8$uOcpr}zr;OYIXOvI5-cbx_WyTkLGmG9J>tE_ZQSm2yDI+HOKrL{v|6r0in>PV zfzGCcbrEZk{_;fXwg*=&^=b8PL>P`rv;SxbK&MW0l;Mu+cc<{gTjWs48=?`qP&xLw zwrLeUqok?sWY!q*u{k@Dv2l;1|JA{?T99~f#Ay}!g$qk9eb`sLLRPJ1Gn^lkvb(Kd-OCqj_onNO=4ak>NqemX1n7$(cl7zmiRbLqSblvU zBf*qC<3ZfU>cg%8OZv<-*rfWM#OSjoyC_b>=(&YCN#|6%WmmC?UM|X~9Put9G*M41 zKQJEs(2tGmFH{xdsX6_5%M>?4h8y*c`53(aPO!iZ%_1};I5>wQl!ZpjMuj;nwp=GT zG@&kHyIg=#V(6wr0E3@TEapyZ$6RLujPavcQck|=pxdh3*^8hOb~=w1J=B|f7Nh2v z33lmn#lzue40Hg4r(TH6s*5WDh5dXySBXSZA~E7)RAvT0b3EcKc-LPb@)JVQ&B&L` z&E6QJ^Q@mZ^zX%6vab6V+RT}VNrri6bHnxAl76Ayn_u2S&td++d(~+ztyJ$c+}Ptf zw!Yix=R2;F0_*y8!}CJHn)QXt?h$(SKRS-jq}tB5T-vni{%J3ugK0>?-qh*$JZ>!P ztPOTJqk2{Es-}})KwMM>J>~3bf6B1L-Q^(3_tUN|T2MkEnmr=)(fD0E+VS%pxQ*O_ zcY<#%iNX0t>GMrtP9 zb(@viPu^D8w+-sag>E|v1UVkD2hA;fl&W${jmopCt2KP>DQO_|xUMXzpP2l!e#}-z zyK1YMPGordv9r(Q2ZxW>(^3wcp7rN1KT_avN84$f;5xq{x{5&q%rZAkz`1w)$<`|u zR`z!N&2ZUd{9bU*lIMKKB2x&JVefXTZG^a~dKNo>O%gkAav?iknwppLO2dMO%;fN7 z`m5~cHcQwa)msdW3_9j+()e|&sOCN8;mP{(qCZ&+=V;GQC$@@+1PZyqm$_+aYetYC zRvEGwZ5?`mn=+B6sKJx5E?nf&VtHg;La6KHk*Rr#!n6D60U^$_q z(=3xRW?jKQz?jlsI%bbdvE$xZ0%6RsIp^dlen&8ml>U!xT!m^i(mqDE+UaD&sgCZ> z@X_g>@F7HAIESB#RBBe(v)p~H>LBA`Cdt*Sb9w4@ECu?}u@27ZXvFao=F`(py|CW9 zh-H2Lu$-ME33k>K-ac7S<|%;6dB^pd=l6>{*n6}m_1B+V#1j<47%QClifI;J9m1Z0x$VsLM%6 z1tsku%rKSWD>kti5)H@?XT=ofp%yjV(sh&?EUcTY4JP3|PttT{|ll7U8|bX$<25(g1J z5!OMO`I<_?WRjC*oQ^@tW;m}TI?DA8(@u+GpGlEX;}?T@jdDqwaHiXBdlHi04B_MQ zor%T|hIcGRDeL0K1tM@Of@2xm_K4=El{-0)I~#sA-komV3$Hd%ABV@S@JwUW+YaZ9 z49691qXy?ZSH2dt*}y+AksCu*X^9>bT9d#M9Tir5NG_45{UhreBkKVFLF0M-38WBX zyROmCo(SuAET@A;WNuB+Rp3-4<-wDEpk@Q*Faqb^f|&VNgTo&`=3!hd_ndcgtPI<9 zgwCks+~G<|_KW)St?eEv?JYfSbJ&6=^J8hDgLipOg^XH_Slm}M<?Jha0ZbFX?tkI|IIXn^^`fCp3xZwcY+9 z7X17C{l^X#lLnX@3o$5(@f*w@(uZ2w6)TtrX|5d}r@ImSBU`A1Cvcdkp~5_06z;8C zy^tWSqE2&LIt%HAIPCbl>2qfSV*7P_!S_bqBt&TLK_u#{0#i!(nrcVYp=#qLQaVlP zA0r_$BBOjux7y;l#(9|6a{O{^ffOY;9Mn#Yss3C)3+{)q@IB`1Sa}}Fab`;s(~(D; zD#}3T(SaVUR2)#7*?RCjFXHG(ybpF)IhbB0T++(FjV~wCqVsSAC)n(rQ>B@zn!Ry@ z(E*b{>@mBs($CSPZs%q!5lZ-Qrb=(_chK2+JPz{?;G>_`2;Tb<@vEcLjuZB@2pcU; z$1x(Mgtxg^OSLHNOT?F3R+3{9M&eb3o`Ud?`8iRyIcNm zlaZczr~qEHHzeok;p^0-k~>}$nCy2l-OBg32WW(NjFzMwiH!Rq4DZJE9%1mLwq&X} zax7BR`_O%(c6ldWy^u3vGih6!~L#_jw13wFlcn z5i;EbuDrhnAI)usXh_(z>B3ihvJHGIo1yj^sZZyx^{<4g$c8y1@~1X^X$}XwudldE z1}dghEu&R5$MSV!tgmiWPzOZP(%A952;iNi&Ax%_Uh58n$+}g&G2CEFEgem;tfFM* zEIyE7UChy_lI^a|yVFP$s1%w{BCo7K#2FSl??(kc&@tHXgr`oV4ohx7Xf&WO^e23U z=~1F#(*EG1WP4IBRhvGo3AIvpPJ%&C$0=p4i0lz8TC*12z zTgedKI;ENrJfTc&+m6w#picG*jehT+a*N`*lcSiWFAgrplT`O~NT`Fg1wRqt2vJr( zw~%w?agTP)74DXX0|4;j>_bl)z0%Rldm*fG&8Bb#eL=0ajQ=2N&MfMyF|wq z#|yP=WbhU9uq~Hu<*Jty=!SR3Je3z2kHuK0oL>}Ce+i)+7?Q)v=F)KuX7dPWyE^{B z1p!YEbWQX>7iG4LhW<>&X>CWVsq0usZefNDT6`J z7ySzpWj0NBROe!-EQiaqwi{>3xzkNmix#PIckPM?bh{o{+z1j@yN+);k%>6*)Qu&QC5 z=_GO068TZRAsxpjzt91sI=L#xm`iQ-1 z{g~C1(B_ZgE7<8$D{Z7yZFa@ilu9}@7sP(#pqL-uCpD&Wjvp9tHC( zpeqh%1}kwPj2`WDyimcT%$>yyHZ3yWt*T5LS8G@|lU;Mdkx0{syi%wTXi2kNYZ;U> z=0q409aXxx3|#S!=Y3IU{Z(^r3p=K?Q^DQeRePmf)e`EgFcaBPRL`oa<9wz1o44H( z3cGvWo~pI6vf-JkHudcG^KAYwcpJxl0x2l_VrS4|cVmPoj+qIaabRnx$wvlO&7Q#8cj;XtgbEN6Y-PFH-*?AHci0XB&dWtM?GIs-ox1GOO*x^-4quT)(L8icQ4E z)Tu1{p`?n9nkKV~uDPoBWEmqm4IK?b<7e>qMkNPF> zz?5&~Jrp2YTFx2A;eB(|atC-uB!JanJAGOT%sh!xN*twN0N31jUngt1Z1INRXiI8|psa$NkgBWU zepM&S^U)ZA4Iadq0jm{r$gbH&Xy^~=nQu?2*wT6*2;bz6*KelI+J!DEoSmV@&xB}G z9u+`?Z3eBOdp2jWYn7H6sy5Oy%mWySB`G={7+|ohjdfnt~l<;yB_?Z2X%+ySX zC5Lxz`m|j_h;{hUlrBSY8*k%=2R;8uO-j5?830O5N(|iy9xPkSeZ=u*X}q9&gHh$& zmrfS;MDY53NQV%g2hYjR6Oo?%!mwqys!^?>eO$Jlp&^bE>w$iIFW&b)yWA~VPvsFUIx|0|d^WTJ{GqGEZYce}gSWSdMT1q&CbF(lx+@LR$gx!5iW2K>S8V*-VGF1ln?r-^? z3ZBirIemQMGT^f%qgJXLrpSU*$fS#z6z{A>FSsAfL989T9Gxy&4K?VAXLcd0c~L~; z0h>s%+PPweXFB=SVT`BS0pX)WDm@W8anJA6Q4KbBny-mExq}vuXRP-?Plak@qdlV9 z&>)dbq(}zKrsuF8HC%Ztia9+>27+XFd>9rIL5Q2`AGM8a`b10svKb9K=JR@i%o$YwaKwU9{2M zHp%xM7sh1Ga^rfujU@b$Szl_>G(83BpIsEPBv)U8XL;bB#& z>1>$#{)CzezG5?9eq^C{=k)uw$o|ol)nE?$8V;1Q+I9DXD7C?oxxD=||FM>rgpzrq zL#t~^&T{lRjalvGE=CzZob^~6kZ=K;^vJ@n5@KCzDem#eYUj!!l58ox2i@y4!GhU$ z2pcp7MUUPl7SL0(Ul(mZ!L*VBj8iF!9CH~VpII)b?PELz86sLUdA z!m11*o9h0iZQJ$xr&9G-q}H7_v?WH#`K@KI7RY}So@oq2CsAKYQ};J(yOmBx=OG(q zMw-sHZlPVQc7wfmK<3mq%Qnvthl5Q`xrvGQd3g7P5dmqa=C(V_SPXjIFuR2<0V(8U zFh0GC3sV!?ng2tUyK^)>%7nK7UcuGUe)EUNS6p{5CfqCTCWeF)W$xJ%tcNO#{P?eR zAl0n~Zl{^X_%HIJCT0Ej5|ylHG?InW#KTC~nuBh5#ub|f4PmSaMw+8Bx7n}w^>lWo zO}2l3VRLr;;|qK@Ub&QGJ%o_9Ch>MACv|GrodnMIFF%xxtvBSP7U#XGdIS}Am14XCBH`iuEQdZW4Vd>kX~p+0Pg z39pZl&-VBefBlmKQL~d&8zaZ?!GZ3uIcg{GO(VB`VL%WCZ+3Hb zp01EH(xS|4NiejXa|u);E`89pz_ZNgS!tNo@ZGnY(wIQt<2fBJo(@{umObBPy(1OD zp>2CYk#(wL8V6#`VPj+s1Xd79Ht&+do582UavnzsWczz%i=;HXKVr4q;SS-QC(a*g zO);%x>*nqn)pFzPsSoau1aso@p# z`Ao}Yaw&dT&Oa5NgrE3_F``PA4&|WLQS(f6PC8;tYwz%bl4n``@0((J^;aK++tgYe zU?hBpQX{Mr@;W;@I~}{kORYUbf?*sb;U%zy)zP?or~MDHNAMypdx6UJI@?vZn)B%L zpE~3gdwiHU?^g)9ia{NP+MKX*oHEf@7U;^q`KafM#rldE|C8^v^t=OcT6T%%xV|Iv zA;RPx0bNdNJo^rCx7jTJc;T+KwNxiL8L6-VX1>YHr|1Ifkde=Ea(CAxUk3ND=v#Rj zo$yV!cjtOx!H1#{4pu$zq|8}O(#b93#iDVj)y@~y`PGs7uW?~y$P7JoRPMD zxJ!sqmP%W3x8&|M;}5;-;~-Br?Lk)%q^9@$laG z>ou;4QP@^q#aaY*(^j*!gpXwcOjme5gwfu6?3E;a=WQzoy2A$HVZkct?x&@fqIG+Q ziB4=TTI)g{lFhIl$>x?Hxy0gpN752J{4Bj8Q?sUhTj8!Q2d(ifaPjVGkGGaeur`u2 zk0MxAcQD8Pqnyhz1zhv&%9X-4oVY5QpT>;uKn2Z}^dE1nm z2a~W7C*e3A93bI2*^zt*bhjtPBKWb z`sv(~(LJvk0oQVmuPEK+$B4d2gsXY2#gr|^JdtU+b9~%o?ZcTf?l!q&UtPT#)S=%z zq7&DdhD^uNfINN4wuz1Wq<7A`3t{yR>l5}n;@*s0^(x{AQBO63JdEND?dRTv#xFe)L7QqGlVG*M%0~qBO`=D1`xJYODM1q zp;*f4sF_gwHTAcj2*qoyVa>s>-3!hH)~wV7t)fch5Msp*SwmkYJO^b(Uec;JT<_J| zYD0w7f_n90-FW?;c*|O}xJXmhFWZ)~-@XVPua+qwGgXGwF+TC^bb6$P^lU*%!zvN(L^txx!E8rM#W zYApQ8u2XhLLVWC#T};@$jO4*tK%Nz|euvJ0STRsQm|aSS^nib0_^GVy&DMM}Ln`8JZ%Zb6z5f$(Y*Z+mlvruJZW8FGnacw9>W zvMioL8>x;RA#;LwpRRb-#EbRy5{-d&S7^N6lb+=9L5|W6{x`<7l=>LsrH=Q%?pRF? z@3pwqIAFrK%Ia&JT8QKGtf1yje#2EaOTz;>C-PP(yZB}vpDt<9ytrRg}H=izJ-e__?UQ6D(g<9LcT?}4)tiS)kp2o;)`dgiP7_)1?&4GG;J0- zgD0qP{#h?eW6M~+|I}nr)BMVTAkBer?wa6kDgA092JYD{AWAIp6 zZD@6(*jqIDG|^?%X=dKX&ig4=l<+{phYMj!b?;Kj_>|lCYMS+pm4kAyXa6x`t&-HT zv7uBg{;^L`t&v>2?&N6C$0$P_5>v*0Fw%03#3W5^z1cokGq)-fW_VU7gYq~-Au0JD zZRgoC7K{h>R(}7BR~yz)XefSe%7jN=1ZA=G`EA!HIn>k24B?Dx3(7+^rcoZFy&hRf z;DY@w_Guz?VRL76F3ua2TNv+~8((v7@*%_7-SB_~)GFSz*%m2D7+ z??rG1&}$jQl7=<}3*mR%OdFrHnm$`t7EJ3&Hk^%Cfy(rJWDbk=zlI1(i9#FGvg-dB zV$gIlAFu0oMdb$H*5hTnS*Lhr3kP%>*T=#irjF8T>EkSyD97A#xka%K>*iZy;+|ym z_Ydf|)^O!8roLNIt2eLRHPLey-(bPLX+?{ke!WOlV57QhEjZ}(dFw=*t!PiNl%%>Z zh`+v1XwaBAy7To!zTAIuT<3n|d^D{{4!Qs!+CR+0i^x|`_o9bG90euj8N)p|EA$n0EcW4Co(7YIp*el_cyt@TZO$Q4ajrOo4~& zFsvK#Y(x@;zDUk#{DXx`#N!0Q`b=%VNyBFTHQ%%-{w)PN zitSSB>THk=w0U#tO*N^{eJ_3Kr2;a@EZJ4!DR7v|c_%uQB%EUbE0@F!u~*4{!DQ=x z_iTh^h5EIt2RFL>6IqepNEz|Bs0ft1TAKiUfqGGJB8;YNrgYCOtnB3|kKy%4q%E{$ zegUT`vrocGl(-Y$EupM8F@aW21b+%0AfT}H*J6Usx9Mp7hrnY?S~Iz&!+GhmUEZ?) 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%0AxAV-Q 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

RS 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 Q0r-=Dt1MIb#wh6j0HgwNt^fc4 diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 00000000..cff821d8 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,684 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "47.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.8" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "4.7.0" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.9.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.5" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "5.8.5" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.5" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + equatable: + dependency: "direct main" + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.4" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + url: "https://pub.dartlang.org" + source: hosted + version: "4.1.2" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "6.11.2" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.2" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.5.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + go_router: + dependency: "direct main" + description: + name: go_router + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.1" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.2" + intl: + dependency: "direct main" + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.12" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + mocktail: + dependency: "direct dev" + description: + name: mocktail + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.2" + path_drawing: + dependency: transitive + description: + name: path_drawing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.7" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.1" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + random_string: + dependency: "direct dev" + description: + name: random_string + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + riverpod: + dependency: transitive + description: + name: riverpod + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + rxdart: + dependency: "direct main" + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.27.6" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.15" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.14" + shared_preferences_ios: + dependency: transitive + description: + name: shared_preferences_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + shelf_static: + dependency: transitive + description: + name: shelf_static + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.11" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + state_notifier: + dependency: transitive + description: + name: state_notifier + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.2+1" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + test: + dependency: transitive + description: + name: test + url: "https://pub.dartlang.org" + source: hosted + version: "1.21.4" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.12" + test_core: + dependency: transitive + description: + name: test_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.16" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + vm_service: + dependency: transitive + description: + name: vm_service + url: "https://pub.dartlang.org" + source: hosted + version: "9.4.0" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+2" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" +sdks: + dart: ">=2.18.0 <3.0.0" + flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4a9a0483..3796f3aa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,97 +1,39 @@ name: starter_architecture_flutter_firebase 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 -# 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.1.0+2 +publish_to: 'none' +version: 2.0.0 environment: - sdk: ">=2.14.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" dependencies: + cloud_firestore: ^4.0.5 + cupertino_icons: ^1.0.5 + equatable: ^2.0.5 + firebase_auth: ^4.1.2 + firebase_core: ^2.2.0 flutter: sdk: flutter - alert_dialogs: - git: - url: https://github.com/bizz84/codewithandrea_flutter_packages - path: packages/alert_dialogs - custom_buttons: - git: - url: https://github.com/bizz84/codewithandrea_flutter_packages - path: packages/custom_buttons - email_password_sign_in_ui: - git: - url: https://github.com/bizz84/codewithandrea_flutter_packages - path: packages/email_password_sign_in_ui - firestore_service: - git: - url: https://github.com/bizz84/codewithandrea_flutter_packages - path: packages/firestore_service - cloud_firestore: ^2.2.2 - cupertino_icons: ^1.0.2 - equatable: ^2.0.3 - firebase_auth: ^1.4.1 - firebase_core: ^1.3.0 - flutter_riverpod: ^1.0.0 - flutter_svg: ^0.22.0 + flutter_riverpod: ^2.1.1 + flutter_svg: ^1.1.6 + go_router: 5.1.1 intl: ^0.17.0 - logger: ^1.0.0 - rxdart: ^0.27.1 - shared_preferences: ^2.0.6 - state_notifier: ^0.7.0 + rxdart: ^0.27.6 + shared_preferences: ^2.0.15 dev_dependencies: - flutter_driver: - sdk: flutter flutter_test: sdk: flutter - build_runner: ^1.11.5 - mocktail: ^0.1.4 - random_string: ^2.3.1 - test: ^1.16.5 - flutter_lints: ^1.0.4 + mocktail: ^0.3.0 + random_string: + flutter_lints: ^2.0.0 -# 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: +# https://stackoverflow.com/a/74234079/436422 +#dependency_overrides: +# firebase_core_platform_interface: 4.5.1 - # 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. +flutter: uses-material-design: true - # To add assets to your application, add an assets section, like this: assets: - assets/time-tracking.svg - # 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/test/mocks.dart b/test/mocks.dart deleted file mode 100644 index 5c53ef28..00000000 --- a/test/mocks.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/material.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:starter_architecture_flutter_firebase/services/shared_preferences_service.dart'; - -class MockFirebaseAuth extends Mock implements FirebaseAuth {} - -class MockUserCredential extends Mock implements UserCredential {} - -class MockNavigatorObserver extends Mock implements NavigatorObserver {} - -class MockSharedPreferences extends Mock implements SharedPreferences {} - -class MockSharedPreferencesService extends Mock - implements SharedPreferencesService {} diff --git a/test/onboarding_view_model_test.dart b/test/onboarding_view_model_test.dart deleted file mode 100644 index b7bc2f1c..00000000 --- a/test/onboarding_view_model_test.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:starter_architecture_flutter_firebase/app/onboarding/onboarding_view_model.dart'; -import 'package:mocktail/mocktail.dart'; -import 'mocks.dart'; - -void main() { - group('OnboardingViewModel', () { - test('OnboardingViewModel loads state from service', () { - final mockService = MockSharedPreferencesService(); - when(() => mockService.isOnboardingComplete()).thenReturn(true); - final vm = OnboardingViewModel(mockService); - expect(vm.isOnboardingComplete, true); - }); - - test('OnboardingViewModel.completeOnboarding() sets state', () async { - final mockService = MockSharedPreferencesService(); - when(() => mockService.isOnboardingComplete()).thenReturn(false); - when(() => mockService.setOnboardingComplete()) - .thenAnswer((_) => Future.value()); - final vm = OnboardingViewModel(mockService); - await vm.completeOnboarding(); - expect(vm.isOnboardingComplete, true); - verify(() => mockService.setOnboardingComplete()).called(1); - }); - }); -} diff --git a/test/shared_preferences_service_test.dart b/test/shared_preferences_service_test.dart deleted file mode 100644 index 3b6d7520..00000000 --- a/test/shared_preferences_service_test.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:starter_architecture_flutter_firebase/services/shared_preferences_service.dart'; -import 'package:mocktail/mocktail.dart'; -import 'mocks.dart'; - -void main() { - group('SharedPreferencesService', () { - test('writes to SharedPreferences', () async { - final preferences = MockSharedPreferences(); - when(() => preferences.setBool( - SharedPreferencesService.onboardingCompleteKey, true)) - .thenAnswer((_) => Future.value(true)); - final service = SharedPreferencesService(preferences); - await service.setOnboardingComplete(); - verify(() => preferences.setBool( - SharedPreferencesService.onboardingCompleteKey, true)); - }); - - test('reads from SharedPreferences (null)', () { - final preferences = MockSharedPreferences(); - when(() => preferences.getBool( - SharedPreferencesService.onboardingCompleteKey)).thenReturn(null); - final service = SharedPreferencesService(preferences); - expect(service.isOnboardingComplete(), false); - }); - test('reads from SharedPreferences (false)', () { - final preferences = MockSharedPreferences(); - when(() => preferences.getBool( - SharedPreferencesService.onboardingCompleteKey)).thenReturn(false); - final service = SharedPreferencesService(preferences); - expect(service.isOnboardingComplete(), false); - }); - test('reads from SharedPreferences (true)', () { - final preferences = MockSharedPreferences(); - when(() => preferences.getBool( - SharedPreferencesService.onboardingCompleteKey)).thenReturn(true); - final service = SharedPreferencesService(preferences); - expect(service.isOnboardingComplete(), true); - }); - }); -} diff --git a/test/sign_in_page_test.dart b/test/sign_in_page_test.dart deleted file mode 100644 index 775761a9..00000000 --- a/test/sign_in_page_test.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:starter_architecture_flutter_firebase/app/top_level_providers.dart'; -import 'package:starter_architecture_flutter_firebase/app/sign_in/sign_in_page.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:starter_architecture_flutter_firebase/routing/app_router.dart'; -import 'mocks.dart'; - -void main() { - setUpAll(() { - registerFallbackValue>( - MaterialPageRoute(builder: (_) => Container())); - }); - - group('sign-in page', () { - late MockFirebaseAuth mockFirebaseAuth; - late MockNavigatorObserver mockNavigatorObserver; - - setUp(() { - mockFirebaseAuth = MockFirebaseAuth(); - mockNavigatorObserver = MockNavigatorObserver(); - }); - - Future pumpSignInPage(WidgetTester tester) async { - await tester.pumpWidget( - ProviderScope( - overrides: [ - firebaseAuthProvider.overrideWithValue(mockFirebaseAuth), - ], - child: Consumer(builder: (context, ref, __) { - final firebaseAuth = ref.watch(firebaseAuthProvider); - return MaterialApp( - home: SignInPage(), - onGenerateRoute: (settings) => - AppRouter.onGenerateRoute(settings, firebaseAuth), - navigatorObservers: [mockNavigatorObserver], - ); - }), - ), - ); - // didPush is called once when the widget is first built - verify(() => mockNavigatorObserver.didPush(any(), any())).called(1); - } - - testWidgets('email & password navigation', (tester) async { - await pumpSignInPage(tester); - - final emailPasswordButton = - find.byKey(SignInPageContents.emailPasswordButtonKey); - expect(emailPasswordButton, findsOneWidget); - - await tester.tap(emailPasswordButton); - await tester.pumpAndSettle(); - - verify(() => mockNavigatorObserver.didPush(captureAny(), captureAny())) - .called(1); - }); - }); -} diff --git a/test/sign_in_view_model_test.dart b/test/sign_in_view_model_test.dart deleted file mode 100644 index f133cfc9..00000000 --- a/test/sign_in_view_model_test.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'dart:async'; - -import 'package:starter_architecture_flutter_firebase/app/sign_in/sign_in_view_model.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'mocks.dart'; - -void main() { - late MockFirebaseAuth mockFirebaseAuth; - late SignInViewModel viewModel; - - setUp(() { - mockFirebaseAuth = MockFirebaseAuth(); - viewModel = SignInViewModel(auth: mockFirebaseAuth); - }); - - void stubSignInAnonymouslyReturnsUser() { - when(() => mockFirebaseAuth.signInAnonymously()) - .thenAnswer((_) => Future.value(MockUserCredential())); - } - - void stubSignInAnonymouslyThrows(Exception exception) { - when(() => mockFirebaseAuth.signInAnonymously()).thenThrow(exception); - } - - test( - 'WHEN view model signs in anonymously' - 'AND auth returns valid user' - 'THEN isLoading is false', () async { - stubSignInAnonymouslyReturnsUser(); - - await viewModel.signInAnonymously(); - - expect(viewModel.isLoading, false); - }); - - test( - 'WHEN view model signs in anonymously' - 'AND auth throws an exception' - 'THEN view model throws an exception' - 'THEN isLoading is false', () async { - final exception = PlatformException(code: 'ERROR_MISSING_PERMISSIONS'); - stubSignInAnonymouslyThrows(exception); - - expect(() => viewModel.signInAnonymously(), throwsA(exception)); - - expect(viewModel.isLoading, false); - }); -} diff --git a/test/src/features/authentication/presentation/account/account_screen_controller_test.dart b/test/src/features/authentication/presentation/account/account_screen_controller_test.dart new file mode 100644 index 00000000..691ea2a1 --- /dev/null +++ b/test/src/features/authentication/presentation/account/account_screen_controller_test.dart @@ -0,0 +1,120 @@ +@Timeout(Duration(milliseconds: 500)) +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/presentation/account/account_screen_controller.dart'; + +import '../../../../mocks.dart'; + +void main() { + ProviderContainer makeProviderContainer(MockAuthRepository authRepository) { + final container = ProviderContainer( + overrides: [ + authRepositoryProvider.overrideWithValue(authRepository), + ], + ); + addTearDown(container.dispose); + return container; + } + + setUpAll(() { + registerFallbackValue(const AsyncLoading()); + }); + + group('AccountScreenController', () { + test('initial state is AsyncData', () { + final authRepository = MockAuthRepository(); + // create the ProviderContainer with the mock auth repository + final container = makeProviderContainer(authRepository); + // create a listener + final listener = Listener>(); + // listen to the provider and call [listener] whenever its value changes + container.listen( + accountScreenControllerProvider, + listener, + fireImmediately: true, + ); + // verify + verify( + // the build method returns a value immediately, so we expect AsyncData + () => listener(null, const AsyncData(null)), + ); + // verify that the listener is no longer called + verifyNoMoreInteractions(listener); + // verify that [signInAnonymously] was not called during initialization + verifyNever(authRepository.signOut); + }); + + test('signOut success', () async { + // setup + final authRepository = MockAuthRepository(); + // stub method to return success + when(authRepository.signOut).thenAnswer((_) => Future.value()); + // create the ProviderContainer with the mock auth repository + final container = makeProviderContainer(authRepository); + // create a listener + final listener = Listener>(); + // listen to the provider and call [listener] whenever its value changes + container.listen( + accountScreenControllerProvider, + listener, + fireImmediately: true, + ); + // sto + const data = AsyncData(null); + // verify initial value from build method + verify(() => listener(null, data)); + // run + final controller = + container.read(accountScreenControllerProvider.notifier); + await controller.signOut(); + // verify + verifyInOrder([ + // set loading state + // * use a matcher since AsyncLoading != AsyncLoading with data + // * https://codewithandrea.com/articles/unit-test-async-notifier-riverpod/ + () => listener(data, any(that: isA())), + // data when complete + () => listener(any(that: isA()), data), + ]); + verifyNoMoreInteractions(listener); + verify(authRepository.signOut).called(1); + }); + test('signOut failure', () async { + // setup + final authRepository = MockAuthRepository(); + // stub method to return success + final exception = Exception('Connection failed'); + when(authRepository.signOut).thenThrow(exception); + // create the ProviderContainer with the mock auth repository + final container = makeProviderContainer(authRepository); + // create a listener + final listener = Listener>(); + // listen to the provider and call [listener] whenever its value changes + container.listen( + accountScreenControllerProvider, + listener, + fireImmediately: true, + ); + const data = AsyncData(null); + // verify initial value from build method + verify(() => listener(null, data)); + // run + final controller = + container.read(accountScreenControllerProvider.notifier); + await controller.signOut(); + // verify + verifyInOrder([ + // set loading state + // * use a matcher since AsyncLoading != AsyncLoading with data + () => listener(data, any(that: isA())), + // error when complete + () => listener( + any(that: isA()), any(that: isA())), + ]); + verifyNoMoreInteractions(listener); + verify(authRepository.signOut).called(1); + }); + }); +} diff --git a/test/job_test.dart b/test/src/features/jobs/domain/job_test.dart similarity index 94% rename from test/job_test.dart rename to test/src/features/jobs/domain/job_test.dart index 0c1d3363..9f34e98c 100644 --- a/test/job_test.dart +++ b/test/src/features/jobs/domain/job_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:starter_architecture_flutter_firebase/app/home/models/job.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/jobs/domain/job.dart'; void main() { group('fromMap', () { diff --git a/test/src/mocks.dart b/test/src/mocks.dart new file mode 100644 index 00000000..df0bf0fe --- /dev/null +++ b/test/src/mocks.dart @@ -0,0 +1,8 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:starter_architecture_flutter_firebase/src/features/authentication/data/firebase_auth_repository.dart'; + +class MockAuthRepository extends Mock implements AuthRepository {} + +class Listener extends Mock { + void call(T? previous, T? next); +} diff --git a/test_driver/app.dart b/test_driver/app.dart deleted file mode 100644 index 487ac679..00000000 --- a/test_driver/app.dart +++ /dev/null @@ -1,34 +0,0 @@ -// import 'package:firebase_core/firebase_core.dart'; -// import 'package:flutter_riverpod/flutter_riverpod.dart'; -// import 'package:mockito/mockito.dart'; -// import 'package:starter_architecture_flutter_firebase/app/top_level_providers.dart'; -// import 'package:starter_architecture_flutter_firebase/main.dart'; -// import 'package:flutter/material.dart'; -// import 'package:flutter_driver/driver_extension.dart'; -// import 'package:starter_architecture_flutter_firebase/services/firestore_database.dart'; - -// import 'fake_auth_service.dart'; - -// class MockDatabase extends Mock implements FirestoreDatabase {} - -// // Run with: -// // flutter drive --target=test_driver/app.dart -// Future main() async { -// // This line enables the extension. -// enableFlutterDriverExtension(); - -// // TODO: Somehow Firebase.initializeApp() is required when running this driver test -// // Need to figure out what code path triggers this as both FirebaseAuth and Firestore are mocked -// WidgetsFlutterBinding.ensureInitialized(); -// await Firebase.initializeApp(); -// // Call the `main()` function of the app, or call `runApp` with -// // any widget you are interested in testing. -// runApp(ProviderScope( -// overrides: [ -// firebaseAuthProvider -// .overrideWithProvider(Provider((ref) => FakeAuthService())), -// databaseProvider.overrideWithProvider(Provider((ref) => MockDatabase())), -// ], -// child: MyApp(), -// )); -// } diff --git a/test_driver/app_test.dart b/test_driver/app_test.dart deleted file mode 100644 index d430c27f..00000000 --- a/test_driver/app_test.dart +++ /dev/null @@ -1,71 +0,0 @@ -// // Some introductory articles about integration tests: -// // http://cogitas.net/write-integration-test-flutter/ -// // https://medium.com/flutter-community/testing-flutter-ui-with-flutter-driver-c1583681e337 -// // https://stackoverflow.com/questions/52462646/how-to-solve-not-found-dartui-error-while-running-integration-tests-on-flutt -// // -// // Issues with opening the drawer with flutter driver -// // https://github.com/flutter/flutter/issues/9002 -// // -// // Rules: -// // - Don't import any flutter code (e.g. material.dart) -// // - Don't import flutter_test.dart - -// import 'package:starter_architecture_flutter_firebase/constants/keys.dart'; -// // Imports the Flutter Driver API. -// import 'package:flutter_driver/flutter_driver.dart'; -// import 'package:test/test.dart'; - -// void main() { -// FlutterDriver driver; -// Future delay([int milliseconds = 250]) async { -// await Future.delayed(Duration(milliseconds: milliseconds)); -// } - -// // Connect to the Flutter driver before running any tests. -// setUpAll(() async { -// driver = await FlutterDriver.connect(); -// }); - -// // Close the connection to the driver after the tests have completed. -// tearDownAll(() async { -// if (driver != null) { -// await driver.close(); -// } -// }); - -// test('check flutter driver health', () async { -// final health = await driver.checkHealth(); -// expect(health.status, HealthStatus.ok); -// }); - -// test('sign in anonymously, sign out', () async { -// // find and tap anonymous sign in button -// final anonymousSignInButton = find.byValueKey(Keys.anonymous); -// // Check to fail early if the auth state is authenticated -// await driver.waitFor(anonymousSignInButton); -// await delay(1000); // for video capture -// await driver.tap(anonymousSignInButton); - -// // Find tab bar and tap on account tab -// // TODO This does not work. See: https://stackoverflow.com/questions/55460993/flutter-driver-test-bottomnavigationbaritem -// final tabBar = find.byValueKey(Keys.tabBar); -// await driver.waitFor(tabBar); -// await delay(1000); // for video capture -// await driver.tap(find.byValueKey(Keys.accountTab)); - -// // find and tap logout button -// final logoutButton = find.byValueKey(Keys.logout); -// await driver.waitFor(logoutButton); -// await delay(1000); // for video capture -// await driver.tap(logoutButton); - -// // find and tap confirm logout button -// final confirmLogoutButton = find.byValueKey(Keys.alertDefault); -// await driver.waitFor(confirmLogoutButton); -// await delay(1000); // for video capture -// await driver.tap(confirmLogoutButton); - -// // try to find anonymous sign in button again -// await driver.waitFor(anonymousSignInButton); -// }); -// } diff --git a/test_driver/fake_auth_service.dart b/test_driver/fake_auth_service.dart deleted file mode 100644 index cfb5b9dd..00000000 --- a/test_driver/fake_auth_service.dart +++ /dev/null @@ -1,287 +0,0 @@ -// import 'dart:async'; - -// import 'package:firebase_auth/firebase_auth.dart'; -// import 'package:firebase_core/firebase_core.dart'; -// import 'package:firebase_auth_platform_interface/src/auth_provider.dart'; -// import 'package:flutter/services.dart'; -// import 'package:meta/meta.dart'; -// import 'package:mockito/mockito.dart'; -// import 'package:random_string/random_string.dart' as random; - -// class MockUser extends Mock implements User {} - -// class MockUserCredential extends Mock implements UserCredential {} - -// /// Fake authentication service to be used for testing the UI -// /// Keeps an in-memory store of registered accounts so that registration and sign in flows can be tested. -// class FakeAuthService implements FirebaseAuth { -// FakeAuthService({ -// this.startupTime = const Duration(milliseconds: 250), -// this.responseTime = const Duration(seconds: 2), -// }) { -// Future.delayed(responseTime).then((_) { -// _add(null); -// }); -// } -// final Duration startupTime; -// final Duration responseTime; - -// final Map _usersStore = {}; - -// User _currentUser; - -// final StreamController _onAuthStateChangedController = -// StreamController(); -// @override -// Stream authStateChanges() => _onAuthStateChangedController.stream; - -// @override -// User get currentUser => _currentUser; - -// UserCredential _mockUserCredential( -// {String uid, String email, String password}) { -// final user = MockUser(); -// when(user.uid).thenReturn(uid); -// when(user.email).thenReturn(email); -// final userCredential = MockUserCredential(); -// when(userCredential.user).thenReturn(user); -// return userCredential; -// } - -// @override -// Future createUserWithEmailAndPassword( -// {required String email, required String password}) async { -// await Future.delayed(responseTime); -// if (_usersStore.keys.contains(email)) { -// throw PlatformException( -// code: 'ERROR_EMAIL_ALREADY_IN_USE', -// message: 'The email address is already registered. Sign in instead?', -// ); -// } -// final userCredential = _mockUserCredential( -// uid: random.randomAlphaNumeric(32), -// email: email, -// password: password, -// ); -// _usersStore[email] = -// _UserData(password: password, user: userCredential.user); -// _add(userCredential.user); -// return userCredential; -// } - -// @override -// Future signInWithCredential(AuthCredential credential) async { -// if (credential is EmailAuthCredential) { -// return signInWithEmailAndPassword( -// email: credential.email, password: credential.password); -// } -// // TODO: implement signInWithCredential -// throw UnimplementedError(); -// } - -// @override -// Future signInWithEmailAndPassword( -// {String email, String password}) async { -// // TODO: implement signInWithEmailAndPassword -// await Future.delayed(responseTime); -// if (!_usersStore.keys.contains(email)) { -// throw PlatformException( -// code: 'ERROR_USER_NOT_FOUND', -// message: 'The email address is not registered. Need an account?', -// ); -// } -// final _UserData _userData = _usersStore[email]; -// if (_userData.password != password) { -// throw PlatformException( -// code: 'ERROR_WRONG_PASSWORD', -// message: 'The password is incorrect. Please try again.', -// ); -// } -// _add(_userData.user); -// final userCredential = _mockUserCredential( -// uid: _userData.user.uid, -// email: email, -// password: password, -// ); -// return userCredential; -// } - -// @override -// Future signOut() async { -// _add(null); -// } - -// void _add(User user) { -// _currentUser = user; -// _onAuthStateChangedController.add(user); -// } - -// @override -// Future signInAnonymously() async { -// await Future.delayed(responseTime); -// final userCredential = _mockUserCredential( -// uid: random.randomAlphaNumeric(32), -// email: null, -// password: null, -// ); -// _add(userCredential.user); -// return userCredential; -// } - -// void dispose() { -// _onAuthStateChangedController.close(); -// } - -// @override -// FirebaseApp app; - -// @override -// Future applyActionCode(String code) { -// // TODO: implement applyActionCode -// throw UnimplementedError(); -// } - -// @override -// Future checkActionCode(String code) { -// // TODO: implement checkActionCode -// throw UnimplementedError(); -// } - -// @override -// Future confirmPasswordReset({String code, String newPassword}) { -// // TODO: implement confirmPasswordReset -// throw UnimplementedError(); -// } - -// @override -// Future> fetchSignInMethodsForEmail(String email) { -// // TODO: implement fetchSignInMethodsForEmail -// throw UnimplementedError(); -// } - -// @override -// Future getRedirectResult() { -// // TODO: implement getRedirectResult -// throw UnimplementedError(); -// } - -// @override -// Stream idTokenChanges() { -// // TODO: implement idTokenChanges -// throw UnimplementedError(); -// } - -// @override -// bool isSignInWithEmailLink(String emailLink) { -// // TODO: implement isSignInWithEmailLink -// throw UnimplementedError(); -// } - -// @override -// // TODO: implement languageCode -// String get languageCode => throw UnimplementedError(); - -// @override -// // TODO: implement onAuthStateChanged -// Stream get onAuthStateChanged => throw UnimplementedError(); - -// @override -// // TODO: implement pluginConstants -// Map get pluginConstants => throw UnimplementedError(); - -// @override -// Future sendSignInLinkToEmail( -// {String email, ActionCodeSettings actionCodeSettings}) { -// // TODO: implement sendSignInLinkToEmail -// throw UnimplementedError(); -// } - -// @override -// Future setLanguageCode(String languageCode) { -// // TODO: implement setLanguageCode -// throw UnimplementedError(); -// } - -// @override -// Future setPersistence(Persistence persistence) { -// // TODO: implement setPersistence -// throw UnimplementedError(); -// } - -// @override -// Future setSettings( -// {bool appVerificationDisabledForTesting, String userAccessGroup}) { -// // TODO: implement setSettings -// throw UnimplementedError(); -// } - -// @override -// Future signInWithCustomToken(String token) { -// // TODO: implement signInWithCustomToken -// throw UnimplementedError(); -// } - -// @override -// Future signInWithEmailLink({String email, String emailLink}) { -// // TODO: implement signInWithEmailLink -// throw UnimplementedError(); -// } - -// @override -// Future signInWithPhoneNumber( -// String phoneNumber, RecaptchaVerifier verifier) { -// // TODO: implement signInWithPhoneNumber -// throw UnimplementedError(); -// } - -// @override -// Future signInWithPopup(AuthProvider provider) { -// // TODO: implement signInWithPopup -// throw UnimplementedError(); -// } - -// @override -// Future signInWithRedirect(AuthProvider provider) { -// // TODO: implement signInWithRedirect -// throw UnimplementedError(); -// } - -// @override -// Stream userChanges() { -// // TODO: implement userChanges -// throw UnimplementedError(); -// } - -// @override -// Future verifyPasswordResetCode(String code) { -// // TODO: implement verifyPasswordResetCode -// throw UnimplementedError(); -// } - -// @override -// Future verifyPhoneNumber( -// {String phoneNumber, -// verificationCompleted, -// verificationFailed, -// codeSent, -// codeAutoRetrievalTimeout, -// String autoRetrievedSmsCodeForTesting, -// Duration timeout = const Duration(seconds: 30), -// int forceResendingToken}) { -// // TODO: implement verifyPhoneNumber -// throw UnimplementedError(); -// } - -// @override -// Future sendPasswordResetEmail( -// {String email, ActionCodeSettings actionCodeSettings}) { -// // TODO: implement sendPasswordResetEmail -// throw UnimplementedError(); -// } -// } - -// class _UserData { -// _UserData({required this.password, required this.user}); -// final String password; -// final User user; -// }