├── .github └── workflows │ └── release_deploy_web.yml ├── .gitignore ├── .vscode ├── launch.json └── mock.rest ├── LICENSE ├── README.md ├── all_lint_rules.yaml ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── brizaldi │ │ │ │ └── flutter_project_template_riverpod │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── images │ ├── 2.0x │ └── flutter_logo.png │ ├── 3.0x │ └── flutter_logo.png │ └── flutter_logo.png ├── flutter_launcher_icons.yaml ├── flutter_native_splash.yaml ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ ├── development.xcscheme │ │ ├── production.xcscheme │ │ └── staging.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── l10n.yaml ├── lib ├── config │ ├── configuration.dart │ └── src │ │ ├── build_config.dart │ │ └── env.dart ├── constants │ ├── assets.dart │ └── strings.dart ├── features │ ├── auth │ │ ├── application │ │ │ ├── auth │ │ │ │ ├── auth_notifier.dart │ │ │ │ └── auth_state.dart │ │ │ ├── sign_in_form │ │ │ │ ├── sign_in_form_notifier.dart │ │ │ │ └── sign_in_form_state.dart │ │ │ └── sign_out │ │ │ │ ├── sign_out_notifier.dart │ │ │ │ └── sign_out_state.dart │ │ ├── domain │ │ │ ├── auth_failure.dart │ │ │ ├── user.dart │ │ │ └── value_objects.dart │ │ ├── infrastructure │ │ │ ├── auth_interceptor.dart │ │ │ ├── auth_remote_service.dart │ │ │ ├── auth_repository.dart │ │ │ ├── auth_response.dart │ │ │ ├── credentials_storage │ │ │ │ ├── credentials_storage.dart │ │ │ │ └── secure_credentials_storage.dart │ │ │ └── dto │ │ │ │ └── user_dto.dart │ │ ├── presentation │ │ │ ├── sign_in_page.dart │ │ │ └── widgets │ │ │ │ ├── sign_in_form.dart │ │ │ │ └── sign_in_scaffold.dart │ │ └── shared │ │ │ └── providers.dart │ ├── core │ │ ├── application │ │ │ └── routes │ │ │ │ ├── route_names.dart │ │ │ │ └── route_notifier.dart │ │ ├── domain │ │ │ ├── errors.dart │ │ │ ├── failures.dart │ │ │ └── value_objects.dart │ │ ├── infrastructure │ │ │ ├── dio_extensions.dart │ │ │ ├── exceptions.dart │ │ │ ├── hive_database.dart │ │ │ └── remote_response.dart │ │ ├── presentation │ │ │ ├── app_widget.dart │ │ │ └── widgets │ │ │ │ ├── alert_helper.dart │ │ │ │ └── loading_overlay.dart │ │ └── shared │ │ │ └── providers.dart │ ├── home │ │ ├── core │ │ │ ├── presentation │ │ │ │ ├── home_page.dart │ │ │ │ └── widgets │ │ │ │ │ └── home_scaffold.dart │ │ │ └── shared │ │ │ │ └── providers.dart │ │ └── counter │ │ │ ├── application │ │ │ └── counter_notifier.dart │ │ │ └── presentation │ │ │ └── counter_page.dart │ └── splash │ │ └── presentation │ │ └── splash_page.dart ├── l10n │ ├── arb │ │ ├── app_en.arb │ │ └── app_id.arb │ └── l10n.dart ├── main.dart ├── style │ ├── src │ │ ├── palette.dart │ │ └── themes.dart │ └── style.dart └── utils │ ├── logging.dart │ ├── string_utils.dart │ ├── validator.dart │ └── value_validators.dart ├── pubspec.lock ├── pubspec.yaml ├── test └── .gitkeep ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.github/workflows/release_deploy_web.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Github Pages 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths-ignore: 7 | - '**/README.md' 8 | pull_request: 9 | branches: [master] 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | env: 16 | FLAVOR: "production" 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: subosito/flutter-action@v1 20 | - run: flutter pub get 21 | - run: flutter packages pub run build_runner build --delete-conflicting-outputs 22 | - uses: bluefireteam/flutter-gh-pages@v7 23 | with: 24 | baseHref: /flutter-project-template-riverpod/ 25 | customArgs: --dart-define="flavor=$FLAVOR" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | .vscode/settings.json 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Dart generated code related 35 | **.gr.dart 36 | **.g.dart 37 | **.freezed.dart 38 | **.mocks.dart 39 | **.config.dart 40 | 41 | # Android related 42 | **/android/**/gradle-wrapper.jar 43 | **/android/.gradle 44 | **/android/captures/ 45 | **/android/gradlew 46 | **/android/gradlew.bat 47 | **/android/local.properties 48 | **/android/**/GeneratedPluginRegistrant.java 49 | 50 | # Android Studio will place build artifacts here 51 | /android/app/debug 52 | /android/app/profile 53 | /android/app/release 54 | 55 | # iOS/XCode related 56 | **/ios/**/*.mode1v3 57 | **/ios/**/*.mode2v3 58 | **/ios/**/*.moved-aside 59 | **/ios/**/*.pbxuser 60 | **/ios/**/*.perspectivev3 61 | **/ios/**/*sync/ 62 | **/ios/**/.sconsign.dblite 63 | **/ios/**/.tags* 64 | **/ios/**/.vagrant/ 65 | **/ios/**/DerivedData/ 66 | **/ios/**/Icon? 67 | **/ios/**/Pods/ 68 | **/ios/**/.symlinks/ 69 | **/ios/**/profile 70 | **/ios/**/xcuserdata 71 | **/ios/.generated/ 72 | **/ios/Flutter/App.framework 73 | **/ios/Flutter/Flutter.framework 74 | **/ios/Flutter/Generated.xcconfig 75 | **/ios/Flutter/app.flx 76 | **/ios/Flutter/app.zip 77 | **/ios/Flutter/flutter_assets/ 78 | **/ios/ServiceDefinitions.json 79 | **/ios/Runner/GeneratedPluginRegistrant.* 80 | 81 | # Web related 82 | lib/generated_plugin_registrant.dart 83 | 84 | # Symbolication related 85 | app.*.symbols 86 | 87 | # Obfuscation related 88 | app.*.map.json -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Flutter: App dev", 9 | "request": "launch", 10 | "type": "dart", 11 | "args": [ 12 | "--flavor", 13 | "development", 14 | "--dart-define", 15 | "flavor=development", 16 | ] 17 | }, 18 | { 19 | "name": "Flutter: App staging", 20 | "request": "launch", 21 | "type": "dart", 22 | "args": [ 23 | "--flavor", 24 | "staging", 25 | "--dart-define", 26 | "flavor=staging", 27 | ] 28 | }, 29 | { 30 | "name": "Flutter: App production", 31 | "request": "launch", 32 | "type": "dart", 33 | "args": [ 34 | "--flavor", 35 | "production", 36 | "--dart-define", 37 | "flavor=production", 38 | ] 39 | }, 40 | { 41 | "name": "Dart: Run all Tests", 42 | "type": "dart", 43 | "request": "launch", 44 | "program": "./test/" 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /.vscode/mock.rest: -------------------------------------------------------------------------------- 1 | @baseUrl = https://6128855786a213001729f948.mockapi.io/api/v1 2 | 3 | POST {{baseUrl}}/login 4 | content-type: application/json 5 | 6 | { 7 | "email": "john@gmail.com", 8 | "password": "12345678" 9 | } 10 | ### 11 | GET {{baseUrl}}/logout 12 | content-type: application/json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Bahri Rizaldi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter-project-template-riverpod 2 | 3 | ## Requirements 4 | Flutter: >=3.16.0 5 | Dart SDK: >=3.2.0 6 | 7 | ## Supported Platforms 8 | | Android | iOS | Web | Windows | MacOS | Linux | 9 | | :-----: | :---: | :---: | :-----: | :---: | :---: | 10 | | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | 11 | 12 | ## Live Demo 13 | https://brizaldi.github.io/flutter-project-template-riverpod/ 14 | 15 | ## Installation 16 | - Add [Flutter](https://flutter.dev/docs/get-started/install 'Flutter') to your machine 17 | - Open this project folder with Terminal/CMD 18 | - Ensure there's no cache/build leftover by running `flutter clean` in the Terminal 19 | - Run in the Terminal `flutter packages get` 20 | - Run in the Terminal `dart run build_runner build --delete-conflicting-outputs` 21 | 22 | ## Additional steps for iOS 23 | - Open ios folder inside Terminal/CMD 24 | - Run in the Terminal `pod install` 25 | - Run in the Terminal `pod update` 26 | 27 | ## Running the App 28 | - Open Android Emulator or iOS Simulator 29 | - Run `flutter run --flavor {RELEASE_TYPE} --dart-define flavor={RELEASE_TYPE}` 30 | - Supported release type: `development`, `staging`, and `production` 31 | 32 | ## Build an APK 33 | - Run `flutter build apk --flavor {RELEASE_TYPE}` 34 | - The apk will be saved under this location: `[project]/build/app/outputs/flutter-apk/` 35 | - We can also build appbundle (.aab) by running this command: `flutter build appbundle --flavor {RELEASE_TYPE} --dart-define flavor={RELEASE_TYPE}` 36 | 37 | ## Build for iOS 38 | - Follow the tutorial from this link: https://flutter.dev/docs/deployment/ios#create-a-build-archive-with-xcode 39 | - Don't forget to add the release type behind the build command 40 | - For example `flutter build ipa --flavor {RELEASE_TYPE} --dart-define flavor={RELEASE_TYPE}` 41 | 42 | For more information, check out the [official documentation](https://flutter.dev/docs 'documentation') 43 | -------------------------------------------------------------------------------- /all_lint_rules.yaml: -------------------------------------------------------------------------------- 1 | linter: 2 | rules: 3 | - always_declare_return_types 4 | - always_put_control_body_on_new_line 5 | - always_put_required_named_parameters_first 6 | - always_require_non_null_named_parameters 7 | - always_specify_types 8 | - always_use_package_imports 9 | - annotate_overrides 10 | - annotate_redeclares 11 | - avoid_annotating_with_dynamic 12 | - avoid_bool_literals_in_conditional_expressions 13 | - avoid_catches_without_on_clauses 14 | - avoid_catching_errors 15 | - avoid_classes_with_only_static_members 16 | - avoid_double_and_int_checks 17 | - avoid_dynamic_calls 18 | - avoid_empty_else 19 | - avoid_equals_and_hash_code_on_mutable_classes 20 | - avoid_escaping_inner_quotes 21 | - avoid_field_initializers_in_const_classes 22 | - avoid_final_parameters 23 | - avoid_function_literals_in_foreach_calls 24 | - avoid_implementing_value_types 25 | - avoid_init_to_null 26 | - avoid_js_rounded_ints 27 | - avoid_multiple_declarations_per_line 28 | - avoid_null_checks_in_equality_operators 29 | - avoid_positional_boolean_parameters 30 | - avoid_print 31 | - avoid_private_typedef_functions 32 | - avoid_redundant_argument_values 33 | - avoid_relative_lib_imports 34 | - avoid_renaming_method_parameters 35 | - avoid_return_types_on_setters 36 | - avoid_returning_null 37 | - avoid_returning_null_for_future 38 | - avoid_returning_null_for_void 39 | - avoid_returning_this 40 | - avoid_setters_without_getters 41 | - avoid_shadowing_type_parameters 42 | - avoid_single_cascade_in_expression_statements 43 | - avoid_slow_async_io 44 | - avoid_type_to_string 45 | - avoid_types_as_parameter_names 46 | - avoid_types_on_closure_parameters 47 | - avoid_unnecessary_containers 48 | - avoid_unstable_final_fields 49 | - avoid_unused_constructor_parameters 50 | - avoid_void_async 51 | - avoid_web_libraries_in_flutter 52 | - await_only_futures 53 | - camel_case_extensions 54 | - camel_case_types 55 | - cancel_subscriptions 56 | - cascade_invocations 57 | - cast_nullable_to_non_nullable 58 | - close_sinks 59 | - collection_methods_unrelated_type 60 | - combinators_ordering 61 | - comment_references 62 | - conditional_uri_does_not_exist 63 | - constant_identifier_names 64 | - control_flow_in_finally 65 | - curly_braces_in_flow_control_structures 66 | - dangling_library_doc_comments 67 | - depend_on_referenced_packages 68 | - deprecated_consistency 69 | - deprecated_member_use_from_same_package 70 | - diagnostic_describe_all_properties 71 | - directives_ordering 72 | - discarded_futures 73 | - do_not_use_environment 74 | - empty_catches 75 | - empty_constructor_bodies 76 | - empty_statements 77 | - eol_at_end_of_file 78 | - exhaustive_cases 79 | - file_names 80 | - flutter_style_todos 81 | - hash_and_equals 82 | - implementation_imports 83 | - implicit_call_tearoffs 84 | - implicit_reopen 85 | - invalid_case_patterns 86 | - iterable_contains_unrelated_type 87 | - join_return_with_assignment 88 | - leading_newlines_in_multiline_strings 89 | - library_annotations 90 | - library_names 91 | - library_prefixes 92 | - library_private_types_in_public_api 93 | - lines_longer_than_80_chars 94 | - list_remove_unrelated_type 95 | - literal_only_boolean_expressions 96 | - matching_super_parameters 97 | - missing_whitespace_between_adjacent_strings 98 | - no_adjacent_strings_in_list 99 | - no_default_cases 100 | - no_duplicate_case_values 101 | - no_leading_underscores_for_library_prefixes 102 | - no_leading_underscores_for_local_identifiers 103 | - no_literal_bool_comparisons 104 | - no_logic_in_create_state 105 | - no_runtimeType_toString 106 | - no_self_assignments 107 | - no_wildcard_variable_uses 108 | - non_constant_identifier_names 109 | - noop_primitive_operations 110 | - null_check_on_nullable_type_parameter 111 | - null_closures 112 | - omit_local_variable_types 113 | - one_member_abstracts 114 | - only_throw_errors 115 | - overridden_fields 116 | - package_api_docs 117 | - package_names 118 | - package_prefixed_library_names 119 | - parameter_assignments 120 | - prefer_adjacent_string_concatenation 121 | - prefer_asserts_in_initializer_lists 122 | - prefer_asserts_with_message 123 | - prefer_collection_literals 124 | - prefer_conditional_assignment 125 | - prefer_const_constructors 126 | - prefer_const_constructors_in_immutables 127 | - prefer_const_declarations 128 | - prefer_const_literals_to_create_immutables 129 | - prefer_constructors_over_static_methods 130 | - prefer_contains 131 | - prefer_double_quotes 132 | - prefer_expression_function_bodies 133 | - prefer_final_fields 134 | - prefer_final_in_for_each 135 | - prefer_final_locals 136 | - prefer_final_parameters 137 | - prefer_for_elements_to_map_fromIterable 138 | - prefer_foreach 139 | - prefer_function_declarations_over_variables 140 | - prefer_generic_function_type_aliases 141 | - prefer_if_elements_to_conditional_expressions 142 | - prefer_if_null_operators 143 | - prefer_initializing_formals 144 | - prefer_inlined_adds 145 | - prefer_int_literals 146 | - prefer_interpolation_to_compose_strings 147 | - prefer_is_empty 148 | - prefer_is_not_empty 149 | - prefer_is_not_operator 150 | - prefer_iterable_whereType 151 | - prefer_mixin 152 | - prefer_null_aware_method_calls 153 | - prefer_null_aware_operators 154 | - prefer_relative_imports 155 | - prefer_single_quotes 156 | - prefer_spread_collections 157 | - prefer_typing_uninitialized_variables 158 | - prefer_void_to_null 159 | - provide_deprecation_message 160 | - public_member_api_docs 161 | - recursive_getters 162 | - require_trailing_commas 163 | - secure_pubspec_urls 164 | - sized_box_for_whitespace 165 | - sized_box_shrink_expand 166 | - slash_for_doc_comments 167 | - sort_child_properties_last 168 | - sort_constructors_first 169 | - sort_pub_dependencies 170 | - sort_unnamed_constructors_first 171 | - test_types_in_equals 172 | - throw_in_finally 173 | - tighten_type_of_initializing_formals 174 | - type_annotate_public_apis 175 | - type_init_formals 176 | - type_literal_in_constant_pattern 177 | - unawaited_futures 178 | - unnecessary_await_in_return 179 | - unnecessary_brace_in_string_interps 180 | - unnecessary_breaks 181 | - unnecessary_const 182 | - unnecessary_constructor_name 183 | - unnecessary_final 184 | - unnecessary_getters_setters 185 | - unnecessary_lambdas 186 | - unnecessary_late 187 | - unnecessary_library_directive 188 | - unnecessary_new 189 | - unnecessary_null_aware_assignments 190 | - unnecessary_null_aware_operator_on_extension_on_nullable 191 | - unnecessary_null_checks 192 | - unnecessary_null_in_if_null_operators 193 | - unnecessary_nullable_for_final_variable_declarations 194 | - unnecessary_overrides 195 | - unnecessary_parenthesis 196 | - unnecessary_raw_strings 197 | - unnecessary_statements 198 | - unnecessary_string_escapes 199 | - unnecessary_string_interpolations 200 | - unnecessary_this 201 | - unnecessary_to_list_in_spreads 202 | - unreachable_from_main 203 | - unrelated_type_equality_checks 204 | - unsafe_html 205 | - use_build_context_synchronously 206 | - use_colored_box 207 | - use_decorated_box 208 | - use_enums 209 | - use_full_hex_values_for_flutter_colors 210 | - use_function_type_syntax_for_parameters 211 | - use_if_null_to_convert_nulls_to_bools 212 | - use_is_even_rather_than_modulo 213 | - use_key_in_widget_constructors 214 | - use_late_for_private_fields_and_variables 215 | - use_named_constants 216 | - use_raw_strings 217 | - use_rethrow_when_possible 218 | - use_setters_to_change_properties 219 | - use_string_buffers 220 | - use_string_in_part_of_directives 221 | - use_super_parameters 222 | - use_test_throws_matchers 223 | - use_to_and_as_if_applicable 224 | - valid_regexps 225 | - void_checks -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | namespace "com.brizaldi.flutter_project_template_riverpod" 27 | compileSdkVersion 34 28 | ndkVersion flutter.ndkVersion 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 45 | applicationId "com.brizaldi.flutter_project_template_riverpod" 46 | // You can update the following values to match your application needs. 47 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 48 | minSdkVersion 21 49 | targetSdkVersion flutter.targetSdkVersion 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | } 53 | 54 | buildTypes { 55 | release { 56 | // TODO: Add your own signing config for the release build. 57 | // Signing with the debug keys for now, so `flutter run --release` works. 58 | signingConfig signingConfigs.debug 59 | } 60 | } 61 | 62 | flavorDimensions "default" 63 | 64 | productFlavors { 65 | development { 66 | applicationIdSuffix ".dev" 67 | versionNameSuffix = "-dev" 68 | resValue "string", "app_name", "App Template Dev" 69 | } 70 | staging { 71 | applicationIdSuffix ".staging" 72 | versionNameSuffix = "-staging" 73 | resValue "string", "app_name", "App Template Stg" 74 | } 75 | production { 76 | resValue "string", "app_name", "App Template" 77 | } 78 | } 79 | } 80 | 81 | flutter { 82 | source '../..' 83 | } 84 | 85 | dependencies {} 86 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/brizaldi/flutter_project_template_riverpod/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.brizaldi.flutter_project_template_riverpod 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | import io.flutter.embedding.engine.FlutterEngine 5 | import io.flutter.plugin.common.MethodChannel 6 | 7 | class MainActivity: FlutterActivity() { 8 | override fun configureFlutterEngine(flutterEngine: FlutterEngine) { 9 | super.configureFlutterEngine(flutterEngine) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | mavenCentral() 17 | } 18 | } 19 | 20 | rootProject.buildDir = '../build' 21 | subprojects { 22 | project.buildDir = "${rootProject.buildDir}/${project.name}" 23 | } 24 | subprojects { 25 | project.evaluationDependsOn(':app') 26 | } 27 | 28 | tasks.register("clean", Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 21 | } 22 | } 23 | 24 | plugins { 25 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 26 | id "com.android.application" version "7.3.0" apply false 27 | } 28 | 29 | include ":app" 30 | -------------------------------------------------------------------------------- /assets/images/2.0x/flutter_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/assets/images/2.0x/flutter_logo.png -------------------------------------------------------------------------------- /assets/images/3.0x/flutter_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/assets/images/3.0x/flutter_logo.png -------------------------------------------------------------------------------- /assets/images/flutter_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/assets/images/flutter_logo.png -------------------------------------------------------------------------------- /flutter_launcher_icons.yaml: -------------------------------------------------------------------------------- 1 | # Run it using below commands: 2 | # flutter pub get 3 | # flutter pub run flutter_launcher_icons:main 4 | 5 | flutter_icons: 6 | android: "launcher_icon" 7 | ios: true 8 | image_path_android: "assets/images/ios_launcher_icon.png" 9 | image_path_ios: "assets/images/ios_launcher_icon.png" 10 | adaptive_icon_background: "#ffffff" 11 | adaptive_icon_foreground: "assets/images/android_launcher_icon.png" -------------------------------------------------------------------------------- /flutter_native_splash.yaml: -------------------------------------------------------------------------------- 1 | flutter_native_splash: 2 | 3 | # This package generates native code to customize Flutter's default white native splash screen 4 | # with background color and splash image. 5 | # Customize the parameters below, and run the following command in the terminal: 6 | # flutter pub run flutter_native_splash:create 7 | # To restore Flutter's default white splash screen, run the following command in the terminal: 8 | # flutter pub run flutter_native_splash:remove 9 | 10 | # color is the only required parameter. It sets the background color of your splash screen. 11 | color: "#ffffff" 12 | 13 | # Optional parameters are listed below. To enable a parameter, uncomment the line by removing 14 | # the leading # character. 15 | 16 | # The image parameter allows you to specify an image used in the splash screen. It must be a 17 | # png file. 18 | image: assets/images/splash_logo.png 19 | 20 | # The color_dark and image_dark are parameters that set the color and image when the device is 21 | # in dark mode. If they are not specified, the app will use the color and image above. 22 | # If the image_dark parameter is specified, color_dark must be specified. 23 | #color_dark: "#042a49" 24 | #image_dark: assets/splash-invert.png 25 | 26 | # The android and ios parameters can be used to disable generating a splash screen on a given 27 | # platform. 28 | #android: false 29 | #ios: false 30 | #web: false 31 | 32 | # The position of the splash image can be set with android_gravity, ios_content_mode, and 33 | # web_image_mode parameters. All default to center. 34 | # 35 | # android_gravity can be one of the following Android Gravity (see 36 | # https://developer.android.com/reference/android/view/Gravity): bottom, center, 37 | # center_horizontal, center_vertical, clip_horizontal, clip_vertical, end, fill, fill_horizontal, 38 | # fill_vertical, left, right, start, or top. 39 | #android_gravity: center 40 | # 41 | # ios_content_mode can be one of the following iOS UIView.ContentMode (see 42 | # https://developer.apple.com/documentation/uikit/uiview/contentmode): scaleToFill, 43 | # scaleAspectFit, scaleAspectFill, center, top, bottom, left, right, topLeft, topRight, 44 | # bottomLeft, or bottomRight. 45 | #ios_content_mode: center 46 | # 47 | # web_image_mode can be one of the following modes: center, contain, stretch, and cover. 48 | #web_image_mode: center 49 | 50 | # To hide the notification bar, use the fullscreen parameter. Has no affect in web since web 51 | # has no notification bar. Defaults to false. 52 | # NOTE: Unlike Android, iOS will not automatically show the notification bar when the app loads. 53 | # To show the notification bar, add the following code to your Flutter app: 54 | # WidgetsFlutterBinding.ensureInitialized(); 55 | # SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom, SystemUiOverlay.top]); 56 | #fullscreen: true 57 | 58 | # If you have changed the name(s) of your info.plist file(s), you can specify the filename(s) 59 | # with the info_plist_files parameter. Remove only the # characters in the three lines below, 60 | # do not remove any spaces: 61 | #info_plist_files: 62 | # - 'ios/Runner/Info-Debug.plist' 63 | # - 'ios/Runner/Info-Release.plist' -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | $(APP_NAME) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(APP_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | Flavor 26 | $(APP_FLAVOR) 27 | LSRequiresIPhoneOS 28 | 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | CFBundleLocalizations 47 | 48 | en 49 | id 50 | 51 | CADisableMinimumFrameDurationOnPhone 52 | 53 | UIApplicationSupportsIndirectInputEvents 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n/arb 2 | template-arb-file: app_en.arb 3 | output-localization-file: app_localizations.dart 4 | nullable-getter: false -------------------------------------------------------------------------------- /lib/config/configuration.dart: -------------------------------------------------------------------------------- 1 | library configuration; 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_web_plugins/url_strategy.dart'; 8 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 9 | import 'package:logging/logging.dart'; 10 | 11 | import '../style/style.dart'; 12 | import '../utils/logging.dart'; 13 | 14 | part 'src/build_config.dart'; 15 | part 'src/env.dart'; 16 | -------------------------------------------------------------------------------- /lib/config/src/build_config.dart: -------------------------------------------------------------------------------- 1 | part of '../configuration.dart'; 2 | 3 | enum Flavor { development, staging, release } 4 | 5 | class BuildConfig { 6 | const BuildConfig._({ 7 | required this.baseUrl, 8 | required this.socketUrl, 9 | required this.connectTimeout, 10 | required this.receiveTimeout, 11 | required this.flavor, 12 | }); 13 | 14 | const BuildConfig._development() 15 | : this._( 16 | baseUrl: 'https://6128855786a213001729f948.mockapi.io/api/v1/', 17 | socketUrl: '', 18 | connectTimeout: const Duration(milliseconds: 20000), 19 | receiveTimeout: const Duration(milliseconds: 20000), 20 | flavor: Flavor.development, 21 | ); 22 | 23 | const BuildConfig._staging() 24 | : this._( 25 | baseUrl: 'https://6128855786a213001729f948.mockapi.io/api/v1/', 26 | socketUrl: '', 27 | connectTimeout: const Duration(milliseconds: 20000), 28 | receiveTimeout: const Duration(milliseconds: 20000), 29 | flavor: Flavor.staging, 30 | ); 31 | 32 | const BuildConfig._release() 33 | : this._( 34 | baseUrl: 'https://6128855786a213001729f948.mockapi.io/api/v1/', 35 | socketUrl: '', 36 | connectTimeout: const Duration(milliseconds: 20000), 37 | receiveTimeout: const Duration(milliseconds: 20000), 38 | flavor: Flavor.release, 39 | ); 40 | 41 | static late BuildConfig _instance; 42 | 43 | static void init({String? flavor}) { 44 | debugPrint( 45 | '╔══════════════════════════════════════════════════════════════╗'); 46 | debugPrint( 47 | ' Build Flavor: $flavor '); 48 | debugPrint( 49 | '╚══════════════════════════════════════════════════════════════╝'); 50 | switch (flavor) { 51 | case 'development': 52 | _instance = const BuildConfig._development(); 53 | case 'staging': 54 | _instance = const BuildConfig._staging(); 55 | default: 56 | _instance = const BuildConfig._release(); 57 | } 58 | unawaited(_initLog()); 59 | } 60 | 61 | static BuildConfig get() { 62 | return _instance; 63 | } 64 | 65 | static Future _initLog() async { 66 | await Log.init(); 67 | switch (_instance.flavor) { 68 | case Flavor.development: 69 | case Flavor.staging: 70 | Log.setLevel(Level.ALL); 71 | case Flavor.release: 72 | Log.setLevel(Level.OFF); 73 | } 74 | } 75 | 76 | final String baseUrl; 77 | final String socketUrl; 78 | final Duration connectTimeout; 79 | final Duration receiveTimeout; 80 | final Flavor flavor; 81 | 82 | static String get flavorName => _instance.flavor.name; 83 | 84 | static bool get isProduction => _instance.flavor == Flavor.release; 85 | 86 | static bool get isStaging => _instance.flavor == Flavor.staging; 87 | 88 | static bool get isDevelopment => _instance.flavor == Flavor.development; 89 | } 90 | -------------------------------------------------------------------------------- /lib/config/src/env.dart: -------------------------------------------------------------------------------- 1 | part of '../configuration.dart'; 2 | 3 | abstract class Env { 4 | Env() { 5 | _init(); 6 | } 7 | 8 | void _init() { 9 | if (kReleaseMode) { 10 | // Used to prevent printing in release mode 11 | debugPrint = (String? message, {int? wrapWidth}) {}; 12 | } 13 | 14 | unawaited(runZonedGuarded(() async { 15 | WidgetsFlutterBinding.ensureInitialized(); 16 | 17 | BuildConfig.init(flavor: const String.fromEnvironment('flavor')); 18 | 19 | Themes.initUiOverlayStyle(); 20 | 21 | usePathUrlStrategy(); 22 | 23 | final app = await onCreate(); 24 | 25 | runApp( 26 | ProviderScope( 27 | child: app, 28 | ), 29 | ); 30 | }, (obj, stack) { 31 | debugPrint(obj.toString()); 32 | debugPrint(stack.toString()); 33 | })); 34 | } 35 | 36 | FutureOr onCreate(); 37 | } 38 | -------------------------------------------------------------------------------- /lib/constants/assets.dart: -------------------------------------------------------------------------------- 1 | class Assets { 2 | static const String appLogo = 'assets/images/app_logo.png'; 3 | } 4 | -------------------------------------------------------------------------------- /lib/constants/strings.dart: -------------------------------------------------------------------------------- 1 | class Strings { 2 | static const String appName = 'Project Template'; 3 | static const String appCodeName = 'project-template'; 4 | 5 | //* Local DB key string 6 | static const String token = 'TOKEN'; 7 | static const String languageCode = 'LANGUAGE_CODE'; 8 | static const String countryCode = 'COUNTRY_CODE'; 9 | } 10 | -------------------------------------------------------------------------------- /lib/features/auth/application/auth/auth_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | 4 | import '../../domain/auth_failure.dart'; 5 | import '../../infrastructure/auth_repository.dart'; 6 | 7 | part 'auth_notifier.freezed.dart'; 8 | part 'auth_state.dart'; 9 | 10 | class AuthNotifier extends StateNotifier { 11 | AuthNotifier(this._repository) : super(const AuthState.initial()); 12 | 13 | final AuthRepository _repository; 14 | 15 | Future checkAndUpdateAuthStatus() async { 16 | final isSignedIn = await _repository.isSignedIn(); 17 | if (isSignedIn) { 18 | state = const AuthState.authenticated(); 19 | } else { 20 | state = const AuthState.unauthenticated(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/features/auth/application/auth/auth_state.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_notifier.dart'; 2 | 3 | @freezed 4 | class AuthState with _$AuthState { 5 | const factory AuthState.initial() = _Initial; 6 | const factory AuthState.authenticated() = _Authenticated; 7 | const factory AuthState.unauthenticated() = _Unauthenticated; 8 | const factory AuthState.failure(AuthFailure failure) = _Failure; 9 | } 10 | -------------------------------------------------------------------------------- /lib/features/auth/application/sign_in_form/sign_in_form_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:fpdart/fpdart.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 4 | 5 | import '../../../../utils/validator.dart'; 6 | import '../../domain/auth_failure.dart'; 7 | import '../../domain/value_objects.dart'; 8 | import '../../infrastructure/auth_repository.dart'; 9 | 10 | part 'sign_in_form_notifier.freezed.dart'; 11 | part 'sign_in_form_state.dart'; 12 | 13 | class SignInFormNotifier extends StateNotifier { 14 | SignInFormNotifier(this._repository) : super(SignInFormState.initial()); 15 | 16 | final AuthRepository _repository; 17 | 18 | void changeEmail(String emailStr) { 19 | state = state.copyWith( 20 | email: Email(emailStr), 21 | failureOrSuccessOption: none(), 22 | ); 23 | } 24 | 25 | void changePassword(String passwordStr) { 26 | state = state.copyWith( 27 | password: Password(passwordStr), 28 | failureOrSuccessOption: none(), 29 | ); 30 | } 31 | 32 | Future signInWithEmailAndPassword() async { 33 | Either? signInFailureOrSuccess; 34 | 35 | if (isValid) { 36 | state = state.copyWith( 37 | isSubmitting: true, 38 | failureOrSuccessOption: none(), 39 | ); 40 | 41 | signInFailureOrSuccess = await _repository.signInWithEmailAndPassword( 42 | email: state.email, 43 | password: state.password, 44 | ); 45 | } 46 | 47 | state = state.copyWith( 48 | isSubmitting: false, 49 | showErrorMessages: true, 50 | failureOrSuccessOption: optionOf(signInFailureOrSuccess), 51 | ); 52 | } 53 | 54 | bool get isValid { 55 | final values = [ 56 | state.email, 57 | state.password, 58 | ]; 59 | 60 | return Validator.validate(values); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/features/auth/application/sign_in_form/sign_in_form_state.dart: -------------------------------------------------------------------------------- 1 | part of 'sign_in_form_notifier.dart'; 2 | 3 | @freezed 4 | class SignInFormState with _$SignInFormState { 5 | const factory SignInFormState({ 6 | required Email email, 7 | required Password password, 8 | required bool showErrorMessages, 9 | required bool isSubmitting, 10 | required Option> failureOrSuccessOption, 11 | }) = _SignInFormState; 12 | 13 | factory SignInFormState.initial() => SignInFormState( 14 | email: Email(''), 15 | password: Password(''), 16 | showErrorMessages: false, 17 | isSubmitting: false, 18 | failureOrSuccessOption: none(), 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /lib/features/auth/application/sign_out/sign_out_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | 4 | import '../../domain/auth_failure.dart'; 5 | import '../../infrastructure/auth_repository.dart'; 6 | 7 | part 'sign_out_notifier.freezed.dart'; 8 | part 'sign_out_state.dart'; 9 | 10 | class SignOutNotifier extends StateNotifier { 11 | SignOutNotifier(this._repository) : super(const SignOutState.initial()); 12 | 13 | final AuthRepository _repository; 14 | 15 | Future signOut() async { 16 | state = const SignOutState.inProgress(); 17 | final failureOrSuccess = await _repository.signOut(); 18 | state = failureOrSuccess.fold( 19 | SignOutState.failure, 20 | (_) => const SignOutState.success(), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/features/auth/application/sign_out/sign_out_state.dart: -------------------------------------------------------------------------------- 1 | part of 'sign_out_notifier.dart'; 2 | 3 | @freezed 4 | class SignOutState with _$SignOutState { 5 | const factory SignOutState.initial() = _Initial; 6 | const factory SignOutState.inProgress() = _InProgress; 7 | const factory SignOutState.success() = _Success; 8 | const factory SignOutState.failure(AuthFailure failure) = _Failure; 9 | } 10 | -------------------------------------------------------------------------------- /lib/features/auth/domain/auth_failure.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'auth_failure.freezed.dart'; 4 | 5 | @freezed 6 | class AuthFailure with _$AuthFailure { 7 | const factory AuthFailure.storage() = _Storage; 8 | const factory AuthFailure.server([int? errorCode, String? message]) = _Server; 9 | const factory AuthFailure.noConnection() = _NoConnection; 10 | } 11 | -------------------------------------------------------------------------------- /lib/features/auth/domain/user.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | import '../../core/domain/value_objects.dart'; 4 | import 'value_objects.dart'; 5 | 6 | part 'user.freezed.dart'; 7 | 8 | @freezed 9 | class User with _$User { 10 | const factory User({ 11 | required UniqueId id, 12 | required Email email, 13 | required UserToken userToken, 14 | }) = _User; 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/auth/domain/value_objects.dart: -------------------------------------------------------------------------------- 1 | import 'package:fpdart/fpdart.dart'; 2 | 3 | import '../../../utils/value_validators.dart'; 4 | import '../../core/domain/failures.dart'; 5 | import '../../core/domain/value_objects.dart'; 6 | 7 | class Email extends ValueObject { 8 | factory Email(String input) { 9 | return Email._( 10 | validateStringNotEmpty(input).flatMap(validateEmail), 11 | ); 12 | } 13 | 14 | const Email._(this.value); 15 | 16 | @override 17 | final Either, String> value; 18 | } 19 | 20 | class Password extends ValueObject { 21 | factory Password(String input) { 22 | return Password._( 23 | validatePassword(input), 24 | ); 25 | } 26 | 27 | const Password._(this.value); 28 | 29 | @override 30 | final Either, String> value; 31 | } 32 | 33 | class UserToken extends ValueObject { 34 | factory UserToken(String input) { 35 | return UserToken._( 36 | validateStringNotEmpty(input), 37 | ); 38 | } 39 | 40 | const UserToken._(this.value); 41 | 42 | @override 43 | final Either, String> value; 44 | } 45 | -------------------------------------------------------------------------------- /lib/features/auth/infrastructure/auth_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | import 'auth_repository.dart'; 4 | 5 | class AuthInterceptor extends Interceptor { 6 | AuthInterceptor(this._repository); 7 | 8 | final AuthRepository _repository; 9 | 10 | @override 11 | Future onRequest( 12 | RequestOptions options, 13 | RequestInterceptorHandler handler, 14 | ) async { 15 | final storedCredentials = await _repository.getSignedInCredentials(); 16 | 17 | final RequestOptions modifiedOptions = options 18 | ..headers.addAll( 19 | storedCredentials == null 20 | ? {} 21 | : {'Authorization': 'bearer $storedCredentials'}, 22 | ); 23 | 24 | handler.next(modifiedOptions); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/features/auth/infrastructure/auth_remote_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | import '../../core/infrastructure/dio_extensions.dart'; 4 | import '../../core/infrastructure/exceptions.dart'; 5 | import 'auth_response.dart'; 6 | 7 | class AuthRemoteService { 8 | AuthRemoteService(this._dio); 9 | 10 | final Dio _dio; 11 | 12 | Future signOut() async { 13 | try { 14 | await _dio.get('logout'); 15 | } on DioException catch (e) { 16 | if (e.isNoConnectionError || e.isConnectionTimeout) { 17 | throw NoConnectionException(); 18 | } else if (e.response != null) { 19 | throw RestApiException(e.response?.statusCode); 20 | } else { 21 | rethrow; 22 | } 23 | } 24 | } 25 | 26 | Future signIn({ 27 | required String email, 28 | required String password, 29 | }) async { 30 | try { 31 | final response = await _dio.post>( 32 | 'login', 33 | data: { 34 | 'email': email, 35 | 'password': password, 36 | }, 37 | ); 38 | 39 | if (response.statusCode == 201) { 40 | final token = response.data?['token'] as String?; 41 | 42 | if (token != null) { 43 | return AuthResponse.withToken(token); 44 | } else { 45 | return const AuthResponse.failure( 46 | errorCode: 404, 47 | message: 'Credential token not found', 48 | ); 49 | } 50 | } else { 51 | final message = response.data?['message'] as String?; 52 | return AuthResponse.failure( 53 | errorCode: response.statusCode, 54 | message: message, 55 | ); 56 | } 57 | } on DioException catch (e) { 58 | if (e.isNoConnectionError || e.isConnectionTimeout) { 59 | throw NoConnectionException(); 60 | } else if (e.response != null) { 61 | throw RestApiException(e.response?.statusCode); 62 | } else { 63 | rethrow; 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/features/auth/infrastructure/auth_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:fpdart/fpdart.dart'; 3 | 4 | import '../../core/infrastructure/exceptions.dart'; 5 | import '../domain/auth_failure.dart'; 6 | import '../domain/value_objects.dart'; 7 | import 'auth_remote_service.dart'; 8 | import 'credentials_storage/credentials_storage.dart'; 9 | 10 | class AuthRepository { 11 | AuthRepository( 12 | this._credentialsStorage, 13 | this._remoteService, 14 | ); 15 | 16 | final CredentialsStorage _credentialsStorage; 17 | final AuthRemoteService _remoteService; 18 | 19 | Future isSignedIn() => 20 | getSignedInCredentials().then((credentials) => credentials != null); 21 | 22 | Future> signOut() async { 23 | try { 24 | await _remoteService.signOut(); 25 | } on RestApiException catch (e) { 26 | return left(AuthFailure.server(e.errorCode)); 27 | } on NoConnectionException { 28 | // Ignoring 29 | } 30 | 31 | return clearCredentialsStorage(); 32 | } 33 | 34 | Future> signInWithEmailAndPassword({ 35 | required Email email, 36 | required Password password, 37 | }) async { 38 | try { 39 | final emailStr = email.getOrCrash(); 40 | final passwordStr = password.getOrCrash(); 41 | 42 | final authResponse = await _remoteService.signIn( 43 | email: emailStr, 44 | password: passwordStr, 45 | ); 46 | 47 | return authResponse.when( 48 | withToken: (token) async { 49 | await _credentialsStorage.save(token); 50 | return right(unit); 51 | }, 52 | failure: (errorCode, message) => left(AuthFailure.server( 53 | errorCode, 54 | message, 55 | )), 56 | ); 57 | } on RestApiException catch (e) { 58 | return left(AuthFailure.server(e.errorCode)); 59 | } on NoConnectionException { 60 | return left(const AuthFailure.noConnection()); 61 | } 62 | } 63 | 64 | Future getSignedInCredentials() async { 65 | try { 66 | final storedCredentials = await _credentialsStorage.read(); 67 | 68 | return storedCredentials; 69 | } on PlatformException { 70 | return null; 71 | } 72 | } 73 | 74 | Future> clearCredentialsStorage() async { 75 | try { 76 | await _credentialsStorage.clear(); 77 | return right(unit); 78 | } on PlatformException { 79 | return left(const AuthFailure.storage()); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/features/auth/infrastructure/auth_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'auth_response.freezed.dart'; 4 | 5 | @freezed 6 | class AuthResponse with _$AuthResponse { 7 | const factory AuthResponse.withToken(String token) = _WithToken; 8 | const factory AuthResponse.failure({ 9 | int? errorCode, 10 | String? message, 11 | }) = _Failure; 12 | 13 | const AuthResponse._(); 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/auth/infrastructure/credentials_storage/credentials_storage.dart: -------------------------------------------------------------------------------- 1 | abstract class CredentialsStorage { 2 | Future read(); 3 | 4 | Future save(String credentials); 5 | 6 | Future clear(); 7 | } 8 | -------------------------------------------------------------------------------- /lib/features/auth/infrastructure/credentials_storage/secure_credentials_storage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_secure_storage/flutter_secure_storage.dart'; 2 | 3 | import 'credentials_storage.dart'; 4 | 5 | class SecureCredentialsStorage implements CredentialsStorage { 6 | SecureCredentialsStorage(this._storage); 7 | 8 | final FlutterSecureStorage _storage; 9 | 10 | static const _key = 'credentials'; 11 | 12 | String? _cachedCredentials; 13 | 14 | @override 15 | Future clear() { 16 | _cachedCredentials = null; 17 | return _storage.delete(key: _key); 18 | } 19 | 20 | @override 21 | Future read() async { 22 | if (_cachedCredentials != null) { 23 | return _cachedCredentials; 24 | } 25 | 26 | final storedCredentials = await _storage.read(key: _key); 27 | if (storedCredentials == null) { 28 | return null; 29 | } 30 | 31 | try { 32 | return _cachedCredentials = storedCredentials; 33 | } on FormatException { 34 | return null; 35 | } 36 | } 37 | 38 | @override 39 | Future save(String credentials) { 40 | _cachedCredentials = credentials; 41 | 42 | return _storage.write( 43 | key: _key, 44 | value: credentials, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/features/auth/infrastructure/dto/user_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | import '../../../core/domain/value_objects.dart'; 4 | import '../../domain/user.dart'; 5 | import '../../domain/value_objects.dart'; 6 | 7 | part 'user_dto.freezed.dart'; 8 | part 'user_dto.g.dart'; 9 | 10 | @freezed 11 | class UserDTO with _$UserDTO { 12 | const factory UserDTO({ 13 | required String id, 14 | required String email, 15 | @JsonKey(name: 'token') required String userToken, 16 | }) = _UserDTO; 17 | 18 | const UserDTO._(); 19 | 20 | factory UserDTO.fromDomain(User _) { 21 | return UserDTO( 22 | id: _.id.getOrCrash(), 23 | email: _.email.getOrCrash(), 24 | userToken: _.userToken.getOrCrash(), 25 | ); 26 | } 27 | 28 | factory UserDTO.fromJson(Map json) => 29 | _$UserDTOFromJson(json); 30 | 31 | User toDomain() { 32 | return User( 33 | id: UniqueId(id), 34 | email: Email(email), 35 | userToken: UserToken(userToken), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/sign_in_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:fpdart/fpdart.dart'; 5 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 6 | 7 | import '../../../l10n/l10n.dart'; 8 | import '../../core/presentation/widgets/alert_helper.dart'; 9 | import '../../core/presentation/widgets/loading_overlay.dart'; 10 | import '../domain/auth_failure.dart'; 11 | import '../shared/providers.dart'; 12 | import 'widgets/sign_in_scaffold.dart'; 13 | 14 | class SignInPage extends HookConsumerWidget { 15 | const SignInPage({super.key}); 16 | 17 | @override 18 | Widget build(BuildContext context, WidgetRef ref) { 19 | final l10n = context.l10n; 20 | 21 | ref.listen>>( 22 | signInFormNotifierProvider.select( 23 | (state) => state.failureOrSuccessOption, 24 | ), 25 | (_, failureOrSuccessOption) => failureOrSuccessOption.fold( 26 | () {}, 27 | (either) => either.fold( 28 | (failure) => AlertHelper.showSnackBar( 29 | context, 30 | message: failure.map( 31 | storage: (_) => l10n.storageError, 32 | server: (value) => value.message ?? l10n.serverError, 33 | noConnection: (_) => l10n.noConnectionError, 34 | ), 35 | ), 36 | (_) => unawaited(ref 37 | .read(authNotifierProvider.notifier) 38 | .checkAndUpdateAuthStatus()), 39 | ), 40 | ), 41 | ); 42 | 43 | final isSubmitting = ref.watch( 44 | signInFormNotifierProvider.select((state) => state.isSubmitting), 45 | ); 46 | 47 | return Stack( 48 | children: [ 49 | const SignInScaffold(), 50 | LoadingOverlay(isLoading: isSubmitting), 51 | ], 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/widgets/sign_in_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | 4 | import '../../../../l10n/l10n.dart'; 5 | import '../../shared/providers.dart'; 6 | 7 | class SignInForm extends HookConsumerWidget { 8 | const SignInForm({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context, WidgetRef ref) { 12 | final l10n = context.l10n; 13 | final showErrorMessages = ref.watch( 14 | signInFormNotifierProvider.select((state) => state.showErrorMessages), 15 | ); 16 | 17 | return Form( 18 | autovalidateMode: showErrorMessages 19 | ? AutovalidateMode.always 20 | : AutovalidateMode.disabled, 21 | child: Column( 22 | children: [ 23 | TextFormField( 24 | decoration: const InputDecoration( 25 | prefixIcon: Icon(Icons.email), 26 | labelText: 'Email', 27 | ), 28 | keyboardType: TextInputType.emailAddress, 29 | onChanged: (value) => ref 30 | .read(signInFormNotifierProvider.notifier) 31 | .changeEmail(value), 32 | validator: (_) => 33 | ref.read(signInFormNotifierProvider).email.value.fold( 34 | (f) => f.maybeMap( 35 | invalidEmail: (_) => l10n.validEmailVerificationText, 36 | empty: (_) => l10n.emptyStringVerificationText, 37 | orElse: () => null, 38 | ), 39 | (_) => null, 40 | ), 41 | ), 42 | const SizedBox(height: 8), 43 | TextFormField( 44 | decoration: InputDecoration( 45 | prefixIcon: const Icon(Icons.lock), 46 | labelText: l10n.password, 47 | ), 48 | obscureText: true, 49 | onChanged: (value) => ref 50 | .read(signInFormNotifierProvider.notifier) 51 | .changePassword(value), 52 | validator: (_) => ref 53 | .read(signInFormNotifierProvider) 54 | .password 55 | .value 56 | .fold( 57 | (f) => f.maybeMap( 58 | shortPassword: (_) => l10n.shortPasswordVerificationText, 59 | orElse: () => null, 60 | ), 61 | (_) => null, 62 | ), 63 | ), 64 | ], 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/widgets/sign_in_scaffold.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; 5 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 6 | 7 | import '../../../../l10n/l10n.dart'; 8 | import '../../shared/providers.dart'; 9 | import 'sign_in_form.dart'; 10 | 11 | class SignInScaffold extends HookConsumerWidget { 12 | const SignInScaffold({super.key}); 13 | 14 | @override 15 | Widget build(BuildContext context, WidgetRef ref) { 16 | final l10n = context.l10n; 17 | 18 | return KeyboardDismissOnTap( 19 | child: Scaffold( 20 | appBar: AppBar( 21 | title: Text(l10n.signIn), 22 | ), 23 | body: ListView( 24 | padding: const EdgeInsets.all(16), 25 | children: [ 26 | const SignInForm(), 27 | const SizedBox(height: 24), 28 | TextButton( 29 | onPressed: () { 30 | FocusScope.of(context).unfocus(); 31 | unawaited(ref 32 | .read(signInFormNotifierProvider.notifier) 33 | .signInWithEmailAndPassword()); 34 | }, 35 | child: Text(l10n.signIn), 36 | ), 37 | ], 38 | ), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/features/auth/shared/providers.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_secure_storage/flutter_secure_storage.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | 4 | import '../../core/shared/providers.dart'; 5 | import '../application/auth/auth_notifier.dart'; 6 | import '../application/sign_in_form/sign_in_form_notifier.dart'; 7 | import '../application/sign_out/sign_out_notifier.dart'; 8 | import '../infrastructure/auth_interceptor.dart'; 9 | import '../infrastructure/auth_remote_service.dart'; 10 | import '../infrastructure/auth_repository.dart'; 11 | import '../infrastructure/credentials_storage/credentials_storage.dart'; 12 | import '../infrastructure/credentials_storage/secure_credentials_storage.dart'; 13 | 14 | final flutterSecureStorageProvider = Provider( 15 | (ref) => const FlutterSecureStorage(), 16 | ); 17 | 18 | final credentialsStorageProvider = Provider( 19 | (ref) => SecureCredentialsStorage(ref.watch(flutterSecureStorageProvider)), 20 | ); 21 | 22 | final authRemoteServiceProvider = Provider( 23 | (ref) => AuthRemoteService(ref.watch(dioProvider)), 24 | ); 25 | 26 | final authRepositoryProvider = Provider( 27 | (ref) => AuthRepository( 28 | ref.watch(credentialsStorageProvider), 29 | ref.watch(authRemoteServiceProvider), 30 | ), 31 | ); 32 | 33 | final authInterceptorProvider = Provider( 34 | (ref) => AuthInterceptor(ref.watch(authRepositoryProvider)), 35 | ); 36 | 37 | final authNotifierProvider = StateNotifierProvider( 38 | (ref) => AuthNotifier(ref.watch(authRepositoryProvider)), 39 | ); 40 | 41 | final signInFormNotifierProvider = 42 | StateNotifierProvider.autoDispose( 43 | (ref) => SignInFormNotifier(ref.watch(authRepositoryProvider)), 44 | ); 45 | 46 | final signOutNotifierProvider = 47 | StateNotifierProvider( 48 | (ref) => SignOutNotifier(ref.watch(authRepositoryProvider)), 49 | ); 50 | -------------------------------------------------------------------------------- /lib/features/core/application/routes/route_names.dart: -------------------------------------------------------------------------------- 1 | class RouteNames { 2 | static const defaultRoute = '/'; 3 | static const signInRoute = '/login'; 4 | static const homeRoute = '/home'; 5 | static const counterRoute = 'counter'; 6 | 7 | static const defaultNameRoute = '/'; 8 | static const signInNameRoute = 'login'; 9 | static const homeNameRoute = 'home'; 10 | static const counterNameRoute = 'counter'; 11 | } 12 | -------------------------------------------------------------------------------- /lib/features/core/application/routes/route_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:go_router/go_router.dart'; 3 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 4 | 5 | import '../../../auth/application/auth/auth_notifier.dart'; 6 | import '../../../auth/presentation/sign_in_page.dart'; 7 | import '../../../auth/shared/providers.dart'; 8 | import '../../../home/core/presentation/home_page.dart'; 9 | import '../../../home/counter/presentation/counter_page.dart'; 10 | import '../../../splash/presentation/splash_page.dart'; 11 | import 'route_names.dart'; 12 | 13 | class RouterNotifier extends ChangeNotifier { 14 | RouterNotifier(this._ref) { 15 | _ref.listen( 16 | authNotifierProvider, 17 | (_, __) => notifyListeners(), 18 | ); 19 | } 20 | 21 | final Ref _ref; 22 | 23 | String? redirectLogic(BuildContext context, GoRouterState state) { 24 | final authState = _ref.read(authNotifierProvider); 25 | 26 | final areWeSigningIn = state.matchedLocation == RouteNames.signInRoute; 27 | 28 | return authState.maybeMap( 29 | authenticated: (_) => areWeSigningIn ? RouteNames.homeRoute : null, 30 | orElse: () => areWeSigningIn ? null : RouteNames.signInRoute, 31 | ); 32 | } 33 | 34 | List get routes { 35 | return [ 36 | GoRoute( 37 | name: RouteNames.defaultNameRoute, 38 | path: RouteNames.defaultRoute, 39 | builder: (context, state) => const SplashPage(), 40 | ), 41 | GoRoute( 42 | name: RouteNames.signInNameRoute, 43 | path: RouteNames.signInRoute, 44 | builder: (context, state) => const SignInPage(), 45 | ), 46 | GoRoute( 47 | name: RouteNames.homeNameRoute, 48 | path: RouteNames.homeRoute, 49 | builder: (context, state) => const HomePage(), 50 | routes: [ 51 | GoRoute( 52 | name: RouteNames.counterNameRoute, 53 | path: RouteNames.counterRoute, 54 | builder: (context, state) => const CounterPage(), 55 | ), 56 | ], 57 | ), 58 | ]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/features/core/domain/errors.dart: -------------------------------------------------------------------------------- 1 | import 'failures.dart'; 2 | 3 | class UnexpectedValueError extends Error { 4 | UnexpectedValueError(this.valueFailure); 5 | 6 | final ValueFailure valueFailure; 7 | 8 | @override 9 | String toString() { 10 | const explanation = 11 | 'Encountered a ValueFailure at an unrecoverable point. Terminating.'; 12 | return Error.safeToString('$explanation Failure was: $valueFailure'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/core/domain/failures.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'failures.freezed.dart'; 4 | 5 | @freezed 6 | class ValueFailure with _$ValueFailure { 7 | const factory ValueFailure.exceedingLength({ 8 | required T failedValue, 9 | required int max, 10 | }) = ExceedingLength; 11 | const factory ValueFailure.empty({ 12 | required T failedValue, 13 | }) = Empty; 14 | const factory ValueFailure.multiline({ 15 | required T failedValue, 16 | }) = Multiline; 17 | const factory ValueFailure.invalidEmail({ 18 | required T failedValue, 19 | }) = InvalidEmail; 20 | const factory ValueFailure.shortPassword({ 21 | required T failedValue, 22 | }) = ShortPassword; 23 | } 24 | -------------------------------------------------------------------------------- /lib/features/core/domain/value_objects.dart: -------------------------------------------------------------------------------- 1 | import 'package:fpdart/fpdart.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | import '../../../utils/value_validators.dart'; 5 | import 'errors.dart'; 6 | import 'failures.dart'; 7 | 8 | @immutable 9 | abstract class ValueObject { 10 | const ValueObject(); 11 | Either, T> get value; 12 | 13 | /// Throws [UnexpectedValueError] containing the [ValueFailure] 14 | T getOrCrash() { 15 | return value.fold((f) => throw UnexpectedValueError(f), (right) => right); 16 | } 17 | 18 | Either, Unit> get failureOrUnit { 19 | return value.fold( 20 | left, 21 | (r) => right(unit), 22 | ); 23 | } 24 | 25 | bool isValid() => value.isRight(); 26 | 27 | @override 28 | bool operator ==(Object other) { 29 | if (identical(this, other)) return true; 30 | 31 | return other is ValueObject && other.value == value; 32 | } 33 | 34 | @override 35 | int get hashCode => value.hashCode; 36 | 37 | @override 38 | String toString() => 'Value($value)'; 39 | } 40 | 41 | class UniqueId extends ValueObject { 42 | factory UniqueId(String uniqueId) { 43 | return UniqueId._( 44 | validateStringNotEmpty(uniqueId), 45 | ); 46 | } 47 | 48 | factory UniqueId.fromInteger(int uniqueId) { 49 | return UniqueId._( 50 | right(uniqueId.toString()), 51 | ); 52 | } 53 | 54 | factory UniqueId.empty() => UniqueId('-'); 55 | 56 | const UniqueId._(this.value); 57 | 58 | @override 59 | final Either, String> value; 60 | } 61 | -------------------------------------------------------------------------------- /lib/features/core/infrastructure/dio_extensions.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart'; 4 | 5 | extension DioExceptionX on DioException { 6 | bool get isNoConnectionError { 7 | return type == DioExceptionType.unknown && error is SocketException; 8 | } 9 | 10 | bool get isConnectionTimeout { 11 | return type == DioExceptionType.connectionTimeout || 12 | type == DioExceptionType.receiveTimeout || 13 | type == DioExceptionType.sendTimeout; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/core/infrastructure/exceptions.dart: -------------------------------------------------------------------------------- 1 | class CacheException implements Exception {} 2 | 3 | class RestApiException implements Exception { 4 | RestApiException(this.errorCode); 5 | 6 | final int? errorCode; 7 | } 8 | 9 | class NoConnectionException implements Exception {} 10 | 11 | class UnknownException implements Exception {} 12 | -------------------------------------------------------------------------------- /lib/features/core/infrastructure/hive_database.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive_flutter/hive_flutter.dart'; 2 | 3 | import '../../../constants/strings.dart'; 4 | 5 | class HiveDatabase { 6 | late Box _box; 7 | 8 | Box get box => _box; 9 | 10 | bool _hasBeenInitialized = false; 11 | 12 | Future init() async { 13 | if (_hasBeenInitialized) return; 14 | 15 | _hasBeenInitialized = true; 16 | 17 | await Hive.initFlutter(); 18 | _box = await Hive.openBox(Strings.appCodeName); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/features/core/infrastructure/remote_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'remote_response.freezed.dart'; 4 | 5 | @freezed 6 | class RemoteResponse with _$RemoteResponse { 7 | const factory RemoteResponse.withNewData(T data) = _WithNewData; 8 | const factory RemoteResponse.failure({ 9 | int? errorCode, 10 | String? message, 11 | }) = _Failure; 12 | 13 | const RemoteResponse._(); 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/core/presentation/app_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:fpdart/fpdart.dart'; 4 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 5 | import 'package:pretty_dio_logger/pretty_dio_logger.dart'; 6 | 7 | import '../../../config/configuration.dart'; 8 | import '../../../constants/strings.dart'; 9 | import '../../../l10n/l10n.dart'; 10 | import '../../../style/style.dart'; 11 | import '../../auth/shared/providers.dart'; 12 | import '../shared/providers.dart'; 13 | 14 | final initializationProvider = FutureProvider((ref) async { 15 | await ref.read(hiveProvider).init(); 16 | ref.read(dioProvider) 17 | ..options = BaseOptions( 18 | connectTimeout: BuildConfig.get().connectTimeout, 19 | receiveTimeout: BuildConfig.get().receiveTimeout, 20 | validateStatus: (status) { 21 | return true; 22 | }, 23 | baseUrl: BuildConfig.get().baseUrl, 24 | ) 25 | ..interceptors.add(ref.read(authInterceptorProvider)); 26 | 27 | if (!BuildConfig.isProduction) { 28 | ref.read(dioProvider).interceptors.add(PrettyDioLogger( 29 | requestHeader: true, 30 | requestBody: true, 31 | )); 32 | } 33 | 34 | final authNotifier = ref.read(authNotifierProvider.notifier); 35 | await authNotifier.checkAndUpdateAuthStatus(); 36 | 37 | return unit; 38 | }); 39 | 40 | class AppWidget extends HookConsumerWidget { 41 | const AppWidget({super.key}); 42 | 43 | @override 44 | Widget build(BuildContext context, WidgetRef ref) { 45 | ref.listen(initializationProvider, (_, __) {}); 46 | 47 | final router = ref.watch(routerProvider); 48 | 49 | return MaterialApp.router( 50 | routeInformationProvider: router.routeInformationProvider, 51 | routeInformationParser: router.routeInformationParser, 52 | routerDelegate: router.routerDelegate, 53 | title: Strings.appName, 54 | localizationsDelegates: AppLocalizations.localizationsDelegates, 55 | supportedLocales: AppLocalizations.supportedLocales, 56 | theme: Themes.lightTheme(context), 57 | darkTheme: Themes.darkTheme(context), 58 | themeMode: ThemeMode.light, 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/features/core/presentation/widgets/alert_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flash/flash.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class AlertHelper { 7 | static void showSnackBar(BuildContext context, {required String message}) { 8 | unawaited(showFlash( 9 | context: context, 10 | duration: const Duration(seconds: 2), 11 | builder: (context, controller) { 12 | return FlashBar( 13 | controller: controller, 14 | behavior: FlashBehavior.floating, 15 | content: Text(message), 16 | ); 17 | }, 18 | )); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/features/core/presentation/widgets/loading_overlay.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoadingOverlay extends StatelessWidget { 4 | const LoadingOverlay({ 5 | super.key, 6 | required this.isLoading, 7 | }); 8 | 9 | final bool isLoading; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final theme = Theme.of(context); 14 | final mediaQueryData = MediaQuery.of(context); 15 | 16 | return IgnorePointer( 17 | ignoring: !isLoading, 18 | child: AnimatedContainer( 19 | duration: const Duration(milliseconds: 150), 20 | color: isLoading ? Colors.black.withOpacity(0.8) : Colors.transparent, 21 | width: mediaQueryData.size.width, 22 | height: mediaQueryData.size.height, 23 | child: Visibility( 24 | visible: isLoading, 25 | child: Column( 26 | mainAxisAlignment: MainAxisAlignment.center, 27 | children: [ 28 | const CircularProgressIndicator(), 29 | const SizedBox(height: 8), 30 | Text( 31 | 'Please Wait', 32 | style: theme.textTheme.bodyMedium!.copyWith( 33 | color: Colors.white, 34 | fontSize: 16, 35 | ), 36 | ), 37 | ], 38 | ), 39 | ), 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/features/core/shared/providers.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:go_router/go_router.dart'; 3 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 4 | 5 | import '../application/routes/route_notifier.dart'; 6 | import '../infrastructure/hive_database.dart'; 7 | 8 | final dioProvider = Provider((ref) => Dio()); 9 | 10 | final hiveProvider = Provider( 11 | (ref) => HiveDatabase(), 12 | ); 13 | 14 | final routerProvider = Provider((ref) { 15 | final router = RouterNotifier(ref); 16 | return GoRouter( 17 | refreshListenable: router, 18 | redirect: router.redirectLogic, 19 | routes: router.routes, 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /lib/features/home/core/presentation/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 5 | 6 | import '../../../../l10n/l10n.dart'; 7 | import '../../../auth/application/sign_out/sign_out_notifier.dart'; 8 | import '../../../auth/shared/providers.dart'; 9 | import '../../../core/presentation/widgets/alert_helper.dart'; 10 | import '../../../core/presentation/widgets/loading_overlay.dart'; 11 | import 'widgets/home_scaffold.dart'; 12 | 13 | class HomePage extends HookConsumerWidget { 14 | const HomePage({super.key}); 15 | 16 | @override 17 | Widget build(BuildContext context, WidgetRef ref) { 18 | final l10n = context.l10n; 19 | 20 | ref.listen( 21 | signOutNotifierProvider, 22 | (_, state) => state.maybeWhen( 23 | orElse: () => null, 24 | success: () => unawaited( 25 | ref.read(authNotifierProvider.notifier).checkAndUpdateAuthStatus()), 26 | failure: (failure) => AlertHelper.showSnackBar( 27 | context, 28 | message: failure.map( 29 | storage: (_) => l10n.storageError, 30 | server: (value) => value.message ?? l10n.serverError, 31 | noConnection: (_) => l10n.noConnectionError, 32 | ), 33 | ), 34 | ), 35 | ); 36 | 37 | final signOutState = ref.watch(signOutNotifierProvider); 38 | 39 | return Stack( 40 | children: [ 41 | const HomeScaffold(), 42 | LoadingOverlay( 43 | isLoading: signOutState.maybeWhen( 44 | inProgress: () => true, 45 | orElse: () => false, 46 | ), 47 | ), 48 | ], 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/features/home/core/presentation/widgets/home_scaffold.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:go_router/go_router.dart'; 5 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 6 | 7 | import '../../../../../l10n/l10n.dart'; 8 | import '../../../../auth/shared/providers.dart'; 9 | import '../../../../core/application/routes/route_names.dart'; 10 | import '../../shared/providers.dart'; 11 | 12 | class HomeScaffold extends HookConsumerWidget { 13 | const HomeScaffold({super.key}); 14 | 15 | @override 16 | Widget build(BuildContext context, WidgetRef ref) { 17 | final counterState = ref.watch(counterNotifierProvider); 18 | final l10n = context.l10n; 19 | 20 | return Scaffold( 21 | appBar: AppBar( 22 | title: Text(l10n.home), 23 | actions: [ 24 | IconButton( 25 | onPressed: () => 26 | unawaited(ref.read(signOutNotifierProvider.notifier).signOut()), 27 | icon: const Icon(Icons.logout), 28 | ), 29 | ], 30 | ), 31 | body: Center( 32 | child: Column( 33 | mainAxisSize: MainAxisSize.min, 34 | children: [ 35 | TextButton( 36 | onPressed: () => 37 | ref.read(counterNotifierProvider.notifier).increment(), 38 | child: const Text('Increment'), 39 | ), 40 | Text( 41 | counterState.toString(), 42 | style: Theme.of(context).textTheme.headlineSmall, 43 | ), 44 | ], 45 | ), 46 | ), 47 | floatingActionButton: FloatingActionButton( 48 | onPressed: () => context.goNamed(RouteNames.counterNameRoute), 49 | child: const Icon(Icons.chevron_right), 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/features/home/core/shared/providers.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | 3 | import '../../counter/application/counter_notifier.dart'; 4 | 5 | final counterNotifierProvider = 6 | StateNotifierProvider.autoDispose( 7 | (ref) => CounterNotifier(), 8 | ); 9 | -------------------------------------------------------------------------------- /lib/features/home/counter/application/counter_notifier.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 2 | 3 | class CounterNotifier extends StateNotifier { 4 | CounterNotifier() : super(0); 5 | 6 | void increment() => state = state + 1; 7 | } 8 | -------------------------------------------------------------------------------- /lib/features/home/counter/presentation/counter_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 3 | 4 | import '../../core/shared/providers.dart'; 5 | 6 | class CounterPage extends HookConsumerWidget { 7 | const CounterPage({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context, WidgetRef ref) { 11 | final counterState = ref.watch(counterNotifierProvider); 12 | 13 | return Scaffold( 14 | appBar: AppBar( 15 | title: const Text('Counter'), 16 | ), 17 | body: Center( 18 | child: Column( 19 | mainAxisSize: MainAxisSize.min, 20 | children: [ 21 | TextButton( 22 | onPressed: () => 23 | ref.read(counterNotifierProvider.notifier).increment(), 24 | child: const Text('Increment'), 25 | ), 26 | Text( 27 | counterState.toString(), 28 | style: Theme.of(context).textTheme.headlineSmall, 29 | ), 30 | ], 31 | ), 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/features/splash/presentation/splash_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SplashPage extends StatelessWidget { 4 | const SplashPage({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return const Scaffold( 9 | body: Padding( 10 | padding: EdgeInsets.all(48), 11 | child: Center( 12 | child: LinearProgressIndicator(), 13 | ), 14 | ), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/l10n/arb/app_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": "Project Template", 3 | "storageError": "Oops, there's a problem accessing local storage.", 4 | "home": "Home", 5 | "noConnectionError": "Slow or no network connection. Please check your internet settings.", 6 | "password": "Password", 7 | "serverError": "Server error occured. Please try again later.", 8 | "shortPasswordVerificationText": "Password must have minimum of 6 characters", 9 | "signIn": "Sign In", 10 | "signInStatusError": "Failed getting sign in status", 11 | "signOut": "Sign Out", 12 | "validEmailVerificationText": "Please input a valid email address", 13 | "emptyStringVerificationText": "This field cannot be empty", 14 | "welcomeUser": "Welcome {userName}", 15 | "@welcomeUser": { 16 | "placeholders": { 17 | "userName": {} 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /lib/l10n/arb/app_id.arb: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": "Project Template", 3 | "storageError": "Ups, terjadi kesalahan saat akses ke penyimpanan lokal.", 4 | "home": "Beranda", 5 | "noConnectionError": "Sambungan jaringan lambat atau sedang tidak tersedia. Silakan periksa pengaturan internet anda.", 6 | "password": "Kata sandi", 7 | "serverError": "Terjadi kesalahan server. Silakan coba lagi nanti.", 8 | "shortPasswordVerificationText": "Kata sandi harus memiliki minimal 6 karakter", 9 | "signIn": "Masuk", 10 | "signInStatusError": "Gagal saat mendapatkan status sign in", 11 | "signOut": "Keluar", 12 | "validEmailVerificationText": "Harap masukkan alamat email yang valid", 13 | "emptyStringVerificationText": "Bagian ini tidak boleh kosong", 14 | "welcomeUser": "Selamat datang {userName}", 15 | "@welcomeUser": { 16 | "placeholders": { 17 | "userName": {} 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /lib/l10n/l10n.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; 3 | 4 | export 'package:flutter_gen/gen_l10n/app_localizations.dart'; 5 | 6 | extension AppLocalizationsX on BuildContext { 7 | AppLocalizations get l10n => AppLocalizations.of(this); 8 | } 9 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:hooks_riverpod/hooks_riverpod.dart'; 5 | 6 | import '/config/configuration.dart'; 7 | import 'features/core/presentation/app_widget.dart'; 8 | 9 | void main() => Main(); 10 | 11 | class Main extends Env { 12 | @override 13 | FutureOr onCreate() { 14 | ErrorWidget.builder = (details) { 15 | Zone.current.handleUncaughtError(details.exception, details.stack!); 16 | return const ColoredBox(color: Colors.transparent); 17 | }; 18 | 19 | return const AppWidget(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/style/src/palette.dart: -------------------------------------------------------------------------------- 1 | part of '../style.dart'; 2 | 3 | class Palette { 4 | static const primaryColor = Color(0xff13b9fd); 5 | static const primaryDarkColor = Color(0xff0089ca); 6 | static const primaryLightColor = Color(0xff6debff); 7 | 8 | static const secondaryColor = Color(0xff00d787); 9 | static const secondaryDarkColor = Color(0xff00a459); 10 | static const secondaryLightColor = Color(0xff62ffb7); 11 | 12 | static const primaryTextColor = Color(0xffffffff); 13 | static const secondaryTextColor = Color(0xffffffff); 14 | 15 | static const scaffoldBackgroundColor = Color(0xfff7f7fb); 16 | 17 | static const mystic = Color(0xffdfe3ee); 18 | static const charade = Color(0xff252a33); 19 | static const coralRed = Color(0xffff4242); 20 | static const polar = Color(0xffeffbf9); 21 | } 22 | -------------------------------------------------------------------------------- /lib/style/src/themes.dart: -------------------------------------------------------------------------------- 1 | part of '../style.dart'; 2 | 3 | mixin Themes { 4 | static void initUiOverlayStyle() { 5 | SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( 6 | statusBarColor: Palette.primaryDarkColor, 7 | statusBarIconBrightness: Brightness.light, 8 | systemNavigationBarColor: Colors.black, 9 | systemNavigationBarIconBrightness: Brightness.light, 10 | )); 11 | } 12 | 13 | static ThemeData lightTheme(BuildContext context) { 14 | return ThemeData.light(useMaterial3: false).copyWith( 15 | visualDensity: VisualDensity.adaptivePlatformDensity, 16 | colorScheme: const ColorScheme.light( 17 | primary: Palette.primaryColor, 18 | secondary: Palette.secondaryColor, 19 | onSecondary: Palette.secondaryTextColor, 20 | ), 21 | textTheme: GoogleFonts.latoTextTheme( 22 | Theme.of(context).textTheme, 23 | ), 24 | ); 25 | } 26 | 27 | static ThemeData darkTheme(BuildContext context) { 28 | return ThemeData.dark(useMaterial3: false).copyWith( 29 | visualDensity: VisualDensity.adaptivePlatformDensity, 30 | colorScheme: const ColorScheme.dark( 31 | primary: Palette.primaryColor, 32 | secondary: Palette.secondaryColor, 33 | onPrimary: Palette.primaryTextColor, 34 | onSecondary: Palette.secondaryTextColor, 35 | ), 36 | textTheme: GoogleFonts.latoTextTheme( 37 | Theme.of(context).textTheme, 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/style/style.dart: -------------------------------------------------------------------------------- 1 | library style; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:google_fonts/google_fonts.dart'; 6 | 7 | part 'src/palette.dart'; 8 | part 'src/themes.dart'; 9 | -------------------------------------------------------------------------------- /lib/utils/logging.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:logging/logging.dart'; 3 | 4 | class Log { 5 | static const String _name = 'Logger'; 6 | static late Logger _instance; 7 | 8 | static Future init() async { 9 | Logger.root.onRecord.listen((record) { 10 | debugPrint('${record.level.name}: ${record.time}: ${record.message}'); 11 | }); 12 | _instance = Logger(_name); 13 | } 14 | 15 | // ignore: use_setters_to_change_properties 16 | static void setLevel(Level level) { 17 | Logger.root.level = level; 18 | } 19 | 20 | static void info( 21 | String message, [ 22 | Object? error, 23 | StackTrace? stackTrace, 24 | ]) { 25 | _instance.info(message, error, stackTrace); 26 | } 27 | 28 | static void warning( 29 | String message, [ 30 | Object? error, 31 | StackTrace? stackTrace, 32 | ]) { 33 | _instance.warning(message, error, stackTrace); 34 | } 35 | 36 | static void config( 37 | String message, [ 38 | Object? error, 39 | StackTrace? stackTrace, 40 | ]) { 41 | _instance.config(message, error, stackTrace); 42 | } 43 | 44 | static void fine( 45 | String message, [ 46 | Object? error, 47 | StackTrace? stackTrace, 48 | ]) { 49 | _instance.fine(message, error, stackTrace); 50 | } 51 | 52 | static void finer( 53 | String message, [ 54 | Object? error, 55 | StackTrace? stackTrace, 56 | ]) { 57 | _instance.finer(message, error, stackTrace); 58 | } 59 | 60 | static void finest( 61 | String message, [ 62 | Object? error, 63 | StackTrace? stackTrace, 64 | ]) { 65 | _instance.finest(message, error, stackTrace); 66 | } 67 | 68 | static void severe( 69 | String message, [ 70 | Object? error, 71 | StackTrace? stackTrace, 72 | ]) { 73 | _instance.severe(message, error, stackTrace); 74 | } 75 | 76 | static void shout( 77 | String message, [ 78 | Object? error, 79 | StackTrace? stackTrace, 80 | ]) { 81 | _instance.shout(message, error, stackTrace); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/utils/string_utils.dart: -------------------------------------------------------------------------------- 1 | class StringUtils { 2 | static String capitalize(String value) { 3 | return (value.isNotEmpty) 4 | ? value[0].toUpperCase() + value.substring(1) 5 | : value[0].toUpperCase(); 6 | } 7 | 8 | static String removeAllHtmlTags(String htmlText) { 9 | final pattern = RegExp( 10 | '<.*?>|&.*?;', 11 | multiLine: true, 12 | caseSensitive: false, 13 | ); 14 | return htmlText.replaceAll(pattern, ''); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/utils/validator.dart: -------------------------------------------------------------------------------- 1 | import '../features/core/domain/value_objects.dart'; 2 | 3 | class Validator { 4 | static bool validate(List> values) { 5 | return values.every((value) => value.isValid()); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/utils/value_validators.dart: -------------------------------------------------------------------------------- 1 | import 'package:fpdart/fpdart.dart'; 2 | 3 | import '../features/core/domain/failures.dart'; 4 | 5 | Either, String> validateMaxStringLength( 6 | String input, 7 | int maxLength, 8 | ) { 9 | if (input.length <= maxLength) { 10 | return right(input); 11 | } else { 12 | return left( 13 | ValueFailure.exceedingLength(failedValue: input, max: maxLength), 14 | ); 15 | } 16 | } 17 | 18 | Either, String> validateStringNotEmpty(String input) { 19 | if (input.isNotEmpty) { 20 | return right(input); 21 | } else { 22 | return left(ValueFailure.empty(failedValue: input)); 23 | } 24 | } 25 | 26 | Either, String> validateSingleLine(String input) { 27 | if (input.contains('\n')) { 28 | return left(ValueFailure.multiline(failedValue: input)); 29 | } else { 30 | return right(input); 31 | } 32 | } 33 | 34 | Either, String> validateEmail(String input) { 35 | const emailRegex = 36 | r"""^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+"""; 37 | if (RegExp(emailRegex).hasMatch(input)) { 38 | return right(input); 39 | } else { 40 | return left(ValueFailure.invalidEmail(failedValue: input)); 41 | } 42 | } 43 | 44 | Either, String> validatePassword(String input) { 45 | if (input.length >= 6) { 46 | return right(input); 47 | } else { 48 | return left(ValueFailure.shortPassword(failedValue: input)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "64.0.0" 12 | _flutterfire_internals: 13 | dependency: transitive 14 | description: 15 | name: _flutterfire_internals 16 | sha256: eb0ac20f704799b986049fbb3c1c16421eca319a1b872378d669513e12452ba5 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "1.3.14" 20 | analyzer: 21 | dependency: transitive 22 | description: 23 | name: analyzer 24 | sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "6.2.0" 28 | ansicolor: 29 | dependency: transitive 30 | description: 31 | name: ansicolor 32 | sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880" 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "2.0.2" 36 | archive: 37 | dependency: transitive 38 | description: 39 | name: archive 40 | sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "3.4.9" 44 | args: 45 | dependency: transitive 46 | description: 47 | name: args 48 | sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "2.4.2" 52 | async: 53 | dependency: transitive 54 | description: 55 | name: async 56 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "2.11.0" 60 | awesome_notifications: 61 | dependency: "direct main" 62 | description: 63 | name: awesome_notifications 64 | sha256: "65f730f9c0e73a346039ef746384bcff1773f9f03821b859705a7ab8db977b23" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "0.8.2" 68 | boolean_selector: 69 | dependency: transitive 70 | description: 71 | name: boolean_selector 72 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "2.1.1" 76 | build: 77 | dependency: transitive 78 | description: 79 | name: build 80 | sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "2.4.1" 84 | build_config: 85 | dependency: transitive 86 | description: 87 | name: build_config 88 | sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "1.1.1" 92 | build_daemon: 93 | dependency: transitive 94 | description: 95 | name: build_daemon 96 | sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "4.0.1" 100 | build_resolvers: 101 | dependency: transitive 102 | description: 103 | name: build_resolvers 104 | sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "2.4.1" 108 | build_runner: 109 | dependency: "direct dev" 110 | description: 111 | name: build_runner 112 | sha256: "67d591d602906ef9201caf93452495ad1812bea2074f04e25dbd7c133785821b" 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "2.4.7" 116 | build_runner_core: 117 | dependency: transitive 118 | description: 119 | name: build_runner_core 120 | sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "7.2.11" 124 | built_collection: 125 | dependency: transitive 126 | description: 127 | name: built_collection 128 | sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "5.1.1" 132 | built_value: 133 | dependency: transitive 134 | description: 135 | name: built_value 136 | sha256: "69acb7007eb2a31dc901512bfe0f7b767168be34cb734835d54c070bfa74c1b2" 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "8.8.0" 140 | cached_network_image: 141 | dependency: "direct main" 142 | description: 143 | name: cached_network_image 144 | sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "3.3.0" 148 | cached_network_image_platform_interface: 149 | dependency: transitive 150 | description: 151 | name: cached_network_image_platform_interface 152 | sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613" 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "3.0.0" 156 | cached_network_image_web: 157 | dependency: transitive 158 | description: 159 | name: cached_network_image_web 160 | sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257" 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "1.1.0" 164 | characters: 165 | dependency: transitive 166 | description: 167 | name: characters 168 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 169 | url: "https://pub.dev" 170 | source: hosted 171 | version: "1.3.0" 172 | checked_yaml: 173 | dependency: transitive 174 | description: 175 | name: checked_yaml 176 | sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff 177 | url: "https://pub.dev" 178 | source: hosted 179 | version: "2.0.3" 180 | cli_util: 181 | dependency: transitive 182 | description: 183 | name: cli_util 184 | sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 185 | url: "https://pub.dev" 186 | source: hosted 187 | version: "0.4.0" 188 | clock: 189 | dependency: transitive 190 | description: 191 | name: clock 192 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 193 | url: "https://pub.dev" 194 | source: hosted 195 | version: "1.1.1" 196 | code_builder: 197 | dependency: transitive 198 | description: 199 | name: code_builder 200 | sha256: b2151ce26a06171005b379ecff6e08d34c470180ffe16b8e14b6d52be292b55f 201 | url: "https://pub.dev" 202 | source: hosted 203 | version: "4.8.0" 204 | collection: 205 | dependency: transitive 206 | description: 207 | name: collection 208 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 209 | url: "https://pub.dev" 210 | source: hosted 211 | version: "1.18.0" 212 | convert: 213 | dependency: transitive 214 | description: 215 | name: convert 216 | sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" 217 | url: "https://pub.dev" 218 | source: hosted 219 | version: "3.1.1" 220 | crypto: 221 | dependency: transitive 222 | description: 223 | name: crypto 224 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab 225 | url: "https://pub.dev" 226 | source: hosted 227 | version: "3.0.3" 228 | csslib: 229 | dependency: transitive 230 | description: 231 | name: csslib 232 | sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" 233 | url: "https://pub.dev" 234 | source: hosted 235 | version: "1.0.0" 236 | dart_style: 237 | dependency: transitive 238 | description: 239 | name: dart_style 240 | sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" 241 | url: "https://pub.dev" 242 | source: hosted 243 | version: "2.3.4" 244 | dio: 245 | dependency: "direct main" 246 | description: 247 | name: dio 248 | sha256: "01870acd87986f768e0c09cc4d7a19a59d814af7b34cbeb0b437d2c33bdfea4c" 249 | url: "https://pub.dev" 250 | source: hosted 251 | version: "5.3.4" 252 | fake_async: 253 | dependency: transitive 254 | description: 255 | name: fake_async 256 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 257 | url: "https://pub.dev" 258 | source: hosted 259 | version: "1.3.1" 260 | ffi: 261 | dependency: transitive 262 | description: 263 | name: ffi 264 | sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" 265 | url: "https://pub.dev" 266 | source: hosted 267 | version: "2.1.0" 268 | file: 269 | dependency: transitive 270 | description: 271 | name: file 272 | sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" 273 | url: "https://pub.dev" 274 | source: hosted 275 | version: "7.0.0" 276 | firebase_analytics: 277 | dependency: "direct main" 278 | description: 279 | name: firebase_analytics 280 | sha256: "6325991042226a515406b4fc9204d3102e54a514b72fffb945b625d9f69c5196" 281 | url: "https://pub.dev" 282 | source: hosted 283 | version: "10.7.2" 284 | firebase_analytics_platform_interface: 285 | dependency: transitive 286 | description: 287 | name: firebase_analytics_platform_interface 288 | sha256: "38ad4bec1fa0fe16577e6f178e4c4280f029181b18e16bf68782c527c7d82bfe" 289 | url: "https://pub.dev" 290 | source: hosted 291 | version: "3.8.2" 292 | firebase_analytics_web: 293 | dependency: transitive 294 | description: 295 | name: firebase_analytics_web 296 | sha256: "6c0deb691a26db2006ded65fd8999a204dcfe8ef67d7d75b7f55ebc08e1d89a4" 297 | url: "https://pub.dev" 298 | source: hosted 299 | version: "0.5.5+9" 300 | firebase_core: 301 | dependency: "direct main" 302 | description: 303 | name: firebase_core 304 | sha256: d301561d614487688d797717bef013a264c517d1d09e4c5c1325c3a64c835efb 305 | url: "https://pub.dev" 306 | source: hosted 307 | version: "2.24.0" 308 | firebase_core_platform_interface: 309 | dependency: transitive 310 | description: 311 | name: firebase_core_platform_interface 312 | sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63 313 | url: "https://pub.dev" 314 | source: hosted 315 | version: "5.0.0" 316 | firebase_core_web: 317 | dependency: transitive 318 | description: 319 | name: firebase_core_web 320 | sha256: "10159d9ee42c79f4548971d92f3f0fcd5791f6738cda3583a4e3b2c8b244c018" 321 | url: "https://pub.dev" 322 | source: hosted 323 | version: "2.9.0" 324 | firebase_crashlytics: 325 | dependency: "direct main" 326 | description: 327 | name: firebase_crashlytics 328 | sha256: "60ef0016c0c2a7d16bf02468e3b27cd0ad4606f6d35535998dde3150cc0bc771" 329 | url: "https://pub.dev" 330 | source: hosted 331 | version: "3.4.6" 332 | firebase_crashlytics_platform_interface: 333 | dependency: transitive 334 | description: 335 | name: firebase_crashlytics_platform_interface 336 | sha256: d185100facc6f7c43c5718103111488d008c52df8c19cbc5e5f9d2115d734909 337 | url: "https://pub.dev" 338 | source: hosted 339 | version: "3.6.14" 340 | firebase_messaging: 341 | dependency: "direct main" 342 | description: 343 | name: firebase_messaging 344 | sha256: "260064e1b512a9e1970b5964d645eba888208ca3de42459c38e484c8ecdc37a9" 345 | url: "https://pub.dev" 346 | source: hosted 347 | version: "14.7.6" 348 | firebase_messaging_platform_interface: 349 | dependency: transitive 350 | description: 351 | name: firebase_messaging_platform_interface 352 | sha256: "81fb8c983356dd75ee660f276c918380325df7a1ab1e981ede911809e9ddff30" 353 | url: "https://pub.dev" 354 | source: hosted 355 | version: "4.5.15" 356 | firebase_messaging_web: 357 | dependency: transitive 358 | description: 359 | name: firebase_messaging_web 360 | sha256: "1c5d9b6cf929ab471300143059d1641a26b73c9c24adb5266e241aea23c090aa" 361 | url: "https://pub.dev" 362 | source: hosted 363 | version: "3.5.15" 364 | fixnum: 365 | dependency: transitive 366 | description: 367 | name: fixnum 368 | sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" 369 | url: "https://pub.dev" 370 | source: hosted 371 | version: "1.1.0" 372 | flash: 373 | dependency: "direct main" 374 | description: 375 | name: flash 376 | sha256: "1bb5a53158d7834ce0db8b15a0b948ee369698e59c907b751035c3a787f347ac" 377 | url: "https://pub.dev" 378 | source: hosted 379 | version: "3.1.0" 380 | flutter: 381 | dependency: "direct main" 382 | description: flutter 383 | source: sdk 384 | version: "0.0.0" 385 | flutter_cache_manager: 386 | dependency: transitive 387 | description: 388 | name: flutter_cache_manager 389 | sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" 390 | url: "https://pub.dev" 391 | source: hosted 392 | version: "3.3.1" 393 | flutter_hooks: 394 | dependency: transitive 395 | description: 396 | name: flutter_hooks 397 | sha256: "7c8db779c2d1010aa7f9ea3fbefe8f86524fcb87b69e8b0af31e1a4b55422dec" 398 | url: "https://pub.dev" 399 | source: hosted 400 | version: "0.20.3" 401 | flutter_keyboard_visibility: 402 | dependency: "direct main" 403 | description: 404 | name: flutter_keyboard_visibility 405 | sha256: "4983655c26ab5b959252ee204c2fffa4afeb4413cd030455194ec0caa3b8e7cb" 406 | url: "https://pub.dev" 407 | source: hosted 408 | version: "5.4.1" 409 | flutter_keyboard_visibility_linux: 410 | dependency: transitive 411 | description: 412 | name: flutter_keyboard_visibility_linux 413 | sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" 414 | url: "https://pub.dev" 415 | source: hosted 416 | version: "1.0.0" 417 | flutter_keyboard_visibility_macos: 418 | dependency: transitive 419 | description: 420 | name: flutter_keyboard_visibility_macos 421 | sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 422 | url: "https://pub.dev" 423 | source: hosted 424 | version: "1.0.0" 425 | flutter_keyboard_visibility_platform_interface: 426 | dependency: transitive 427 | description: 428 | name: flutter_keyboard_visibility_platform_interface 429 | sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 430 | url: "https://pub.dev" 431 | source: hosted 432 | version: "2.0.0" 433 | flutter_keyboard_visibility_web: 434 | dependency: transitive 435 | description: 436 | name: flutter_keyboard_visibility_web 437 | sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 438 | url: "https://pub.dev" 439 | source: hosted 440 | version: "2.0.0" 441 | flutter_keyboard_visibility_windows: 442 | dependency: transitive 443 | description: 444 | name: flutter_keyboard_visibility_windows 445 | sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 446 | url: "https://pub.dev" 447 | source: hosted 448 | version: "1.0.0" 449 | flutter_launcher_icons: 450 | dependency: "direct dev" 451 | description: 452 | name: flutter_launcher_icons 453 | sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" 454 | url: "https://pub.dev" 455 | source: hosted 456 | version: "0.13.1" 457 | flutter_localizations: 458 | dependency: "direct main" 459 | description: flutter 460 | source: sdk 461 | version: "0.0.0" 462 | flutter_native_splash: 463 | dependency: "direct dev" 464 | description: 465 | name: flutter_native_splash 466 | sha256: c4d899312b36e7454bedfd0a4740275837b99e532d81c8477579d8183db1de6c 467 | url: "https://pub.dev" 468 | source: hosted 469 | version: "2.3.6" 470 | flutter_riverpod: 471 | dependency: transitive 472 | description: 473 | name: flutter_riverpod 474 | sha256: da9591d1f8d5881628ccd5c25c40e74fc3eef50ba45e40c3905a06e1712412d5 475 | url: "https://pub.dev" 476 | source: hosted 477 | version: "2.4.9" 478 | flutter_secure_storage: 479 | dependency: "direct main" 480 | description: 481 | name: flutter_secure_storage 482 | sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 483 | url: "https://pub.dev" 484 | source: hosted 485 | version: "9.0.0" 486 | flutter_secure_storage_linux: 487 | dependency: transitive 488 | description: 489 | name: flutter_secure_storage_linux 490 | sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e" 491 | url: "https://pub.dev" 492 | source: hosted 493 | version: "1.2.0" 494 | flutter_secure_storage_macos: 495 | dependency: transitive 496 | description: 497 | name: flutter_secure_storage_macos 498 | sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c 499 | url: "https://pub.dev" 500 | source: hosted 501 | version: "3.0.1" 502 | flutter_secure_storage_platform_interface: 503 | dependency: transitive 504 | description: 505 | name: flutter_secure_storage_platform_interface 506 | sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" 507 | url: "https://pub.dev" 508 | source: hosted 509 | version: "1.0.2" 510 | flutter_secure_storage_web: 511 | dependency: transitive 512 | description: 513 | name: flutter_secure_storage_web 514 | sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" 515 | url: "https://pub.dev" 516 | source: hosted 517 | version: "1.1.2" 518 | flutter_secure_storage_windows: 519 | dependency: transitive 520 | description: 521 | name: flutter_secure_storage_windows 522 | sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" 523 | url: "https://pub.dev" 524 | source: hosted 525 | version: "3.0.0" 526 | flutter_test: 527 | dependency: "direct dev" 528 | description: flutter 529 | source: sdk 530 | version: "0.0.0" 531 | flutter_vector_icons: 532 | dependency: "direct main" 533 | description: 534 | name: flutter_vector_icons 535 | sha256: "20a77e282f8cb438c8edcee4eec85d8497d5f4cbeb07d116e9d2d01c9edfc5e2" 536 | url: "https://pub.dev" 537 | source: hosted 538 | version: "2.0.0" 539 | flutter_web_plugins: 540 | dependency: "direct main" 541 | description: flutter 542 | source: sdk 543 | version: "0.0.0" 544 | fpdart: 545 | dependency: "direct main" 546 | description: 547 | name: fpdart 548 | sha256: "7413acc5a6569a3fe8277928fc7487f3198530f0c4e635d0baef199ea36e8ee9" 549 | url: "https://pub.dev" 550 | source: hosted 551 | version: "1.1.0" 552 | freezed: 553 | dependency: "direct dev" 554 | description: 555 | name: freezed 556 | sha256: "21bf2825311de65501d22e563e3d7605dff57fb5e6da982db785ae5372ff018a" 557 | url: "https://pub.dev" 558 | source: hosted 559 | version: "2.4.5" 560 | freezed_annotation: 561 | dependency: "direct main" 562 | description: 563 | name: freezed_annotation 564 | sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d 565 | url: "https://pub.dev" 566 | source: hosted 567 | version: "2.4.1" 568 | frontend_server_client: 569 | dependency: transitive 570 | description: 571 | name: frontend_server_client 572 | sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" 573 | url: "https://pub.dev" 574 | source: hosted 575 | version: "3.2.0" 576 | glob: 577 | dependency: transitive 578 | description: 579 | name: glob 580 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" 581 | url: "https://pub.dev" 582 | source: hosted 583 | version: "2.1.2" 584 | go_router: 585 | dependency: "direct main" 586 | description: 587 | name: go_router 588 | sha256: c247a4f76071c3b97bb5ae8912968870d5565644801c5e09f3bc961b4d874895 589 | url: "https://pub.dev" 590 | source: hosted 591 | version: "12.1.1" 592 | google_fonts: 593 | dependency: "direct main" 594 | description: 595 | name: google_fonts 596 | sha256: f0b8d115a13ecf827013ec9fc883390ccc0e87a96ed5347a3114cac177ef18e8 597 | url: "https://pub.dev" 598 | source: hosted 599 | version: "6.1.0" 600 | graphs: 601 | dependency: transitive 602 | description: 603 | name: graphs 604 | sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 605 | url: "https://pub.dev" 606 | source: hosted 607 | version: "2.3.1" 608 | hive: 609 | dependency: "direct main" 610 | description: 611 | name: hive 612 | sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" 613 | url: "https://pub.dev" 614 | source: hosted 615 | version: "2.2.3" 616 | hive_flutter: 617 | dependency: "direct main" 618 | description: 619 | name: hive_flutter 620 | sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc 621 | url: "https://pub.dev" 622 | source: hosted 623 | version: "1.1.0" 624 | hooks_riverpod: 625 | dependency: "direct main" 626 | description: 627 | name: hooks_riverpod 628 | sha256: c12a456e03ef9be65b0be66963596650ad7a3220e96c7e7b0a048562ea32d6ae 629 | url: "https://pub.dev" 630 | source: hosted 631 | version: "2.4.9" 632 | html: 633 | dependency: transitive 634 | description: 635 | name: html 636 | sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" 637 | url: "https://pub.dev" 638 | source: hosted 639 | version: "0.15.4" 640 | http: 641 | dependency: transitive 642 | description: 643 | name: http 644 | sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 645 | url: "https://pub.dev" 646 | source: hosted 647 | version: "1.1.2" 648 | http_multi_server: 649 | dependency: transitive 650 | description: 651 | name: http_multi_server 652 | sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" 653 | url: "https://pub.dev" 654 | source: hosted 655 | version: "3.2.1" 656 | http_parser: 657 | dependency: transitive 658 | description: 659 | name: http_parser 660 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" 661 | url: "https://pub.dev" 662 | source: hosted 663 | version: "4.0.2" 664 | image: 665 | dependency: transitive 666 | description: 667 | name: image 668 | sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" 669 | url: "https://pub.dev" 670 | source: hosted 671 | version: "4.1.3" 672 | intl: 673 | dependency: "direct main" 674 | description: 675 | name: intl 676 | sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" 677 | url: "https://pub.dev" 678 | source: hosted 679 | version: "0.18.1" 680 | io: 681 | dependency: transitive 682 | description: 683 | name: io 684 | sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" 685 | url: "https://pub.dev" 686 | source: hosted 687 | version: "1.0.4" 688 | jiffy: 689 | dependency: "direct main" 690 | description: 691 | name: jiffy 692 | sha256: cc1d4b75016a9156c29b5d61f0c9176c3e0fb0580cc5a0e0422b5d2cab3fbfff 693 | url: "https://pub.dev" 694 | source: hosted 695 | version: "6.2.1" 696 | js: 697 | dependency: transitive 698 | description: 699 | name: js 700 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 701 | url: "https://pub.dev" 702 | source: hosted 703 | version: "0.6.7" 704 | json_annotation: 705 | dependency: "direct main" 706 | description: 707 | name: json_annotation 708 | sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 709 | url: "https://pub.dev" 710 | source: hosted 711 | version: "4.8.1" 712 | json_serializable: 713 | dependency: "direct dev" 714 | description: 715 | name: json_serializable 716 | sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 717 | url: "https://pub.dev" 718 | source: hosted 719 | version: "6.7.1" 720 | logging: 721 | dependency: "direct main" 722 | description: 723 | name: logging 724 | sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" 725 | url: "https://pub.dev" 726 | source: hosted 727 | version: "1.2.0" 728 | matcher: 729 | dependency: transitive 730 | description: 731 | name: matcher 732 | sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" 733 | url: "https://pub.dev" 734 | source: hosted 735 | version: "0.12.16" 736 | material_color_utilities: 737 | dependency: transitive 738 | description: 739 | name: material_color_utilities 740 | sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" 741 | url: "https://pub.dev" 742 | source: hosted 743 | version: "0.5.0" 744 | meta: 745 | dependency: transitive 746 | description: 747 | name: meta 748 | sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e 749 | url: "https://pub.dev" 750 | source: hosted 751 | version: "1.10.0" 752 | mime: 753 | dependency: transitive 754 | description: 755 | name: mime 756 | sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e 757 | url: "https://pub.dev" 758 | source: hosted 759 | version: "1.0.4" 760 | octo_image: 761 | dependency: transitive 762 | description: 763 | name: octo_image 764 | sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" 765 | url: "https://pub.dev" 766 | source: hosted 767 | version: "2.0.0" 768 | package_config: 769 | dependency: transitive 770 | description: 771 | name: package_config 772 | sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" 773 | url: "https://pub.dev" 774 | source: hosted 775 | version: "2.1.0" 776 | path: 777 | dependency: transitive 778 | description: 779 | name: path 780 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 781 | url: "https://pub.dev" 782 | source: hosted 783 | version: "1.8.3" 784 | path_provider: 785 | dependency: transitive 786 | description: 787 | name: path_provider 788 | sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa 789 | url: "https://pub.dev" 790 | source: hosted 791 | version: "2.1.1" 792 | path_provider_android: 793 | dependency: transitive 794 | description: 795 | name: path_provider_android 796 | sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 797 | url: "https://pub.dev" 798 | source: hosted 799 | version: "2.2.1" 800 | path_provider_foundation: 801 | dependency: transitive 802 | description: 803 | name: path_provider_foundation 804 | sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" 805 | url: "https://pub.dev" 806 | source: hosted 807 | version: "2.3.1" 808 | path_provider_linux: 809 | dependency: transitive 810 | description: 811 | name: path_provider_linux 812 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 813 | url: "https://pub.dev" 814 | source: hosted 815 | version: "2.2.1" 816 | path_provider_platform_interface: 817 | dependency: transitive 818 | description: 819 | name: path_provider_platform_interface 820 | sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" 821 | url: "https://pub.dev" 822 | source: hosted 823 | version: "2.1.1" 824 | path_provider_windows: 825 | dependency: transitive 826 | description: 827 | name: path_provider_windows 828 | sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" 829 | url: "https://pub.dev" 830 | source: hosted 831 | version: "2.2.1" 832 | petitparser: 833 | dependency: transitive 834 | description: 835 | name: petitparser 836 | sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 837 | url: "https://pub.dev" 838 | source: hosted 839 | version: "6.0.1" 840 | platform: 841 | dependency: transitive 842 | description: 843 | name: platform 844 | sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" 845 | url: "https://pub.dev" 846 | source: hosted 847 | version: "3.1.3" 848 | plugin_platform_interface: 849 | dependency: transitive 850 | description: 851 | name: plugin_platform_interface 852 | sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 853 | url: "https://pub.dev" 854 | source: hosted 855 | version: "2.1.7" 856 | pointycastle: 857 | dependency: transitive 858 | description: 859 | name: pointycastle 860 | sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" 861 | url: "https://pub.dev" 862 | source: hosted 863 | version: "3.7.3" 864 | pool: 865 | dependency: transitive 866 | description: 867 | name: pool 868 | sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" 869 | url: "https://pub.dev" 870 | source: hosted 871 | version: "1.5.1" 872 | pretty_dio_logger: 873 | dependency: "direct main" 874 | description: 875 | name: pretty_dio_logger 876 | sha256: "00b80053063935cf9a6190da344c5373b9d0e92da4c944c878ff2fbef0ef6dc2" 877 | url: "https://pub.dev" 878 | source: hosted 879 | version: "1.3.1" 880 | pub_semver: 881 | dependency: transitive 882 | description: 883 | name: pub_semver 884 | sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" 885 | url: "https://pub.dev" 886 | source: hosted 887 | version: "2.1.4" 888 | pubspec_parse: 889 | dependency: transitive 890 | description: 891 | name: pubspec_parse 892 | sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 893 | url: "https://pub.dev" 894 | source: hosted 895 | version: "1.2.3" 896 | riverpod: 897 | dependency: transitive 898 | description: 899 | name: riverpod 900 | sha256: "942999ee48b899f8a46a860f1e13cee36f2f77609eb54c5b7a669bb20d550b11" 901 | url: "https://pub.dev" 902 | source: hosted 903 | version: "2.4.9" 904 | rxdart: 905 | dependency: transitive 906 | description: 907 | name: rxdart 908 | sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" 909 | url: "https://pub.dev" 910 | source: hosted 911 | version: "0.27.7" 912 | shelf: 913 | dependency: transitive 914 | description: 915 | name: shelf 916 | sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 917 | url: "https://pub.dev" 918 | source: hosted 919 | version: "1.4.1" 920 | shelf_web_socket: 921 | dependency: transitive 922 | description: 923 | name: shelf_web_socket 924 | sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" 925 | url: "https://pub.dev" 926 | source: hosted 927 | version: "1.0.4" 928 | sky_engine: 929 | dependency: transitive 930 | description: flutter 931 | source: sdk 932 | version: "0.0.99" 933 | source_gen: 934 | dependency: transitive 935 | description: 936 | name: source_gen 937 | sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 938 | url: "https://pub.dev" 939 | source: hosted 940 | version: "1.4.0" 941 | source_helper: 942 | dependency: transitive 943 | description: 944 | name: source_helper 945 | sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" 946 | url: "https://pub.dev" 947 | source: hosted 948 | version: "1.3.4" 949 | source_span: 950 | dependency: transitive 951 | description: 952 | name: source_span 953 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 954 | url: "https://pub.dev" 955 | source: hosted 956 | version: "1.10.0" 957 | sprintf: 958 | dependency: transitive 959 | description: 960 | name: sprintf 961 | sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" 962 | url: "https://pub.dev" 963 | source: hosted 964 | version: "7.0.0" 965 | sqflite: 966 | dependency: transitive 967 | description: 968 | name: sqflite 969 | sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" 970 | url: "https://pub.dev" 971 | source: hosted 972 | version: "2.3.0" 973 | sqflite_common: 974 | dependency: transitive 975 | description: 976 | name: sqflite_common 977 | sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6 978 | url: "https://pub.dev" 979 | source: hosted 980 | version: "2.5.0+2" 981 | stack_trace: 982 | dependency: transitive 983 | description: 984 | name: stack_trace 985 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 986 | url: "https://pub.dev" 987 | source: hosted 988 | version: "1.11.1" 989 | state_notifier: 990 | dependency: transitive 991 | description: 992 | name: state_notifier 993 | sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb 994 | url: "https://pub.dev" 995 | source: hosted 996 | version: "1.0.0" 997 | stream_channel: 998 | dependency: transitive 999 | description: 1000 | name: stream_channel 1001 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 1002 | url: "https://pub.dev" 1003 | source: hosted 1004 | version: "2.1.2" 1005 | stream_transform: 1006 | dependency: transitive 1007 | description: 1008 | name: stream_transform 1009 | sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" 1010 | url: "https://pub.dev" 1011 | source: hosted 1012 | version: "2.1.0" 1013 | string_scanner: 1014 | dependency: transitive 1015 | description: 1016 | name: string_scanner 1017 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 1018 | url: "https://pub.dev" 1019 | source: hosted 1020 | version: "1.2.0" 1021 | synchronized: 1022 | dependency: transitive 1023 | description: 1024 | name: synchronized 1025 | sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" 1026 | url: "https://pub.dev" 1027 | source: hosted 1028 | version: "3.1.0" 1029 | term_glyph: 1030 | dependency: transitive 1031 | description: 1032 | name: term_glyph 1033 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 1034 | url: "https://pub.dev" 1035 | source: hosted 1036 | version: "1.2.1" 1037 | test_api: 1038 | dependency: transitive 1039 | description: 1040 | name: test_api 1041 | sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" 1042 | url: "https://pub.dev" 1043 | source: hosted 1044 | version: "0.6.1" 1045 | timing: 1046 | dependency: transitive 1047 | description: 1048 | name: timing 1049 | sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" 1050 | url: "https://pub.dev" 1051 | source: hosted 1052 | version: "1.0.1" 1053 | typed_data: 1054 | dependency: transitive 1055 | description: 1056 | name: typed_data 1057 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 1058 | url: "https://pub.dev" 1059 | source: hosted 1060 | version: "1.3.2" 1061 | universal_io: 1062 | dependency: transitive 1063 | description: 1064 | name: universal_io 1065 | sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" 1066 | url: "https://pub.dev" 1067 | source: hosted 1068 | version: "2.2.2" 1069 | uuid: 1070 | dependency: transitive 1071 | description: 1072 | name: uuid 1073 | sha256: df5a4d8f22ee4ccd77f8839ac7cb274ebc11ef9adcce8b92be14b797fe889921 1074 | url: "https://pub.dev" 1075 | source: hosted 1076 | version: "4.2.1" 1077 | vector_math: 1078 | dependency: transitive 1079 | description: 1080 | name: vector_math 1081 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 1082 | url: "https://pub.dev" 1083 | source: hosted 1084 | version: "2.1.4" 1085 | watcher: 1086 | dependency: transitive 1087 | description: 1088 | name: watcher 1089 | sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" 1090 | url: "https://pub.dev" 1091 | source: hosted 1092 | version: "1.1.0" 1093 | web: 1094 | dependency: transitive 1095 | description: 1096 | name: web 1097 | sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 1098 | url: "https://pub.dev" 1099 | source: hosted 1100 | version: "0.3.0" 1101 | web_socket_channel: 1102 | dependency: transitive 1103 | description: 1104 | name: web_socket_channel 1105 | sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b 1106 | url: "https://pub.dev" 1107 | source: hosted 1108 | version: "2.4.0" 1109 | win32: 1110 | dependency: transitive 1111 | description: 1112 | name: win32 1113 | sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f" 1114 | url: "https://pub.dev" 1115 | source: hosted 1116 | version: "5.1.0" 1117 | xdg_directories: 1118 | dependency: transitive 1119 | description: 1120 | name: xdg_directories 1121 | sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" 1122 | url: "https://pub.dev" 1123 | source: hosted 1124 | version: "1.0.3" 1125 | xml: 1126 | dependency: transitive 1127 | description: 1128 | name: xml 1129 | sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 1130 | url: "https://pub.dev" 1131 | source: hosted 1132 | version: "6.4.2" 1133 | yaml: 1134 | dependency: transitive 1135 | description: 1136 | name: yaml 1137 | sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" 1138 | url: "https://pub.dev" 1139 | source: hosted 1140 | version: "3.1.2" 1141 | sdks: 1142 | dart: ">=3.2.0 <4.0.0" 1143 | flutter: ">=3.16.0" 1144 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_project_template_riverpod 2 | description: A new Flutter project. 3 | 4 | # Prevent accidental publishing to pub.dev. 5 | publish_to: 'none' 6 | 7 | version: 1.0.0+1 8 | 9 | environment: 10 | sdk: ">=3.0.0 <4.0.0" 11 | 12 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | flutter_localizations: 16 | sdk: flutter 17 | flutter_web_plugins: 18 | sdk: flutter 19 | 20 | #* UI 21 | cached_network_image: ^3.3.0 22 | flash: ^3.1.0 23 | flutter_keyboard_visibility: ^5.4.1 24 | google_fonts: ^6.1.0 25 | 26 | #* Icons 27 | flutter_vector_icons: ^2.0.0 28 | 29 | #* Firebase 30 | firebase_analytics: ^10.7.2 31 | firebase_core: ^2.24.0 32 | firebase_crashlytics: ^3.4.6 33 | firebase_messaging: ^14.7.6 34 | 35 | #* Local notification 36 | awesome_notifications: ^0.8.2 37 | 38 | #* Network 39 | dio: ^5.3.4 40 | pretty_dio_logger: ^1.3.1 41 | 42 | #* Dart data class 43 | freezed_annotation: ^2.4.1 44 | json_annotation: ^4.8.1 45 | 46 | #* State management 47 | hooks_riverpod: ^2.4.9 48 | 49 | #* Functional programming 50 | fpdart: ^1.1.0 51 | 52 | #* Logging 53 | logging: ^1.2.0 54 | 55 | #* Routing 56 | go_router: ^12.1.1 57 | 58 | #* Persistent storage 59 | flutter_secure_storage: ^9.0.0 60 | hive_flutter: ^1.1.0 61 | hive: ^2.2.3 62 | 63 | #* Localization 64 | intl: ^0.18.1 65 | 66 | #* Date Time util 67 | jiffy: ^6.2.1 68 | 69 | dependency_overrides: 70 | intl: ^0.18.0 71 | 72 | dev_dependencies: 73 | flutter_test: 74 | sdk: flutter 75 | 76 | #* Code generators 77 | build_runner: ^2.4.7 78 | flutter_launcher_icons: ^0.13.1 79 | flutter_native_splash: ^2.3.6 80 | freezed: ^2.4.5 81 | json_serializable: ^6.7.1 82 | 83 | flutter: 84 | # The following line ensures that the Material Icons font is 85 | # included with your application, so that you can use the icons in 86 | # the material Icons class. 87 | uses-material-design: true 88 | 89 | # Enable generation of localized Strings from arb files. 90 | generate: true 91 | 92 | assets: 93 | # Add assets from the images directory to the application. 94 | - assets/images/ 95 | -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/test/.gitkeep -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | flutter_project_template_riverpod 34 | 35 | 36 | 40 | 41 | 42 | 43 | 44 | 45 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter_project_template_riverpod", 3 | "short_name": "flutter_project_template_riverpod", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(flutter_project_template_riverpod LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "flutter_project_template_riverpod") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(VERSION 3.14...3.25) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | 56 | # Generated plugin build rules, which manage building the plugins and adding 57 | # them to the application. 58 | include(flutter/generated_plugins.cmake) 59 | 60 | 61 | # === Installation === 62 | # Support files are copied into place next to the executable, so that it can 63 | # run in place. This is done instead of making a separate bundle (as on Linux) 64 | # so that building and running from within Visual Studio will work. 65 | set(BUILD_BUNDLE_DIR "$") 66 | # Make the "install" step default, as it's required to run. 67 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 68 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 69 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 70 | endif() 71 | 72 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 73 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 74 | 75 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 76 | COMPONENT Runtime) 77 | 78 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 82 | COMPONENT Runtime) 83 | 84 | if(PLUGIN_BUNDLED_LIBRARIES) 85 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 86 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 87 | COMPONENT Runtime) 88 | endif() 89 | 90 | # Copy the native assets provided by the build.dart from all packages. 91 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") 92 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 93 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 94 | COMPONENT Runtime) 95 | 96 | # Fully re-copy the assets directory on each build to avoid having stale files 97 | # from a previous install. 98 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 99 | install(CODE " 100 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 101 | " COMPONENT Runtime) 102 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 103 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 104 | 105 | # Install the AOT library on non-Debug builds only. 106 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 107 | CONFIGURATIONS Profile;Release 108 | COMPONENT Runtime) 109 | -------------------------------------------------------------------------------- /windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # Set fallback configurations for older versions of the flutter tool. 14 | if (NOT DEFINED FLUTTER_TARGET_PLATFORM) 15 | set(FLUTTER_TARGET_PLATFORM "windows-x64") 16 | endif() 17 | 18 | # === Flutter Library === 19 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 20 | 21 | # Published to parent scope for install step. 22 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 23 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 24 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 25 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 26 | 27 | list(APPEND FLUTTER_LIBRARY_HEADERS 28 | "flutter_export.h" 29 | "flutter_windows.h" 30 | "flutter_messenger.h" 31 | "flutter_plugin_registrar.h" 32 | "flutter_texture_registrar.h" 33 | ) 34 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 35 | add_library(flutter INTERFACE) 36 | target_include_directories(flutter INTERFACE 37 | "${EPHEMERAL_DIR}" 38 | ) 39 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 40 | add_dependencies(flutter flutter_assemble) 41 | 42 | # === Wrapper === 43 | list(APPEND CPP_WRAPPER_SOURCES_CORE 44 | "core_implementations.cc" 45 | "standard_codec.cc" 46 | ) 47 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 48 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 49 | "plugin_registrar.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 52 | list(APPEND CPP_WRAPPER_SOURCES_APP 53 | "flutter_engine.cc" 54 | "flutter_view_controller.cc" 55 | ) 56 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 57 | 58 | # Wrapper sources needed for a plugin. 59 | add_library(flutter_wrapper_plugin STATIC 60 | ${CPP_WRAPPER_SOURCES_CORE} 61 | ${CPP_WRAPPER_SOURCES_PLUGIN} 62 | ) 63 | apply_standard_settings(flutter_wrapper_plugin) 64 | set_target_properties(flutter_wrapper_plugin PROPERTIES 65 | POSITION_INDEPENDENT_CODE ON) 66 | set_target_properties(flutter_wrapper_plugin PROPERTIES 67 | CXX_VISIBILITY_PRESET hidden) 68 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 69 | target_include_directories(flutter_wrapper_plugin PUBLIC 70 | "${WRAPPER_ROOT}/include" 71 | ) 72 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 73 | 74 | # Wrapper sources needed for the runner. 75 | add_library(flutter_wrapper_app STATIC 76 | ${CPP_WRAPPER_SOURCES_CORE} 77 | ${CPP_WRAPPER_SOURCES_APP} 78 | ) 79 | apply_standard_settings(flutter_wrapper_app) 80 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 81 | target_include_directories(flutter_wrapper_app PUBLIC 82 | "${WRAPPER_ROOT}/include" 83 | ) 84 | add_dependencies(flutter_wrapper_app flutter_assemble) 85 | 86 | # === Flutter tool backend === 87 | # _phony_ is a non-existent file to force this command to run every time, 88 | # since currently there's no way to get a full input/output list from the 89 | # flutter tool. 90 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 91 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 92 | add_custom_command( 93 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 94 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 95 | ${CPP_WRAPPER_SOURCES_APP} 96 | ${PHONY_OUTPUT} 97 | COMMAND ${CMAKE_COMMAND} -E env 98 | ${FLUTTER_TOOL_ENVIRONMENT} 99 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 100 | ${FLUTTER_TARGET_PLATFORM} $ 101 | VERBATIM 102 | ) 103 | add_custom_target(flutter_assemble DEPENDS 104 | "${FLUTTER_LIBRARY}" 105 | ${FLUTTER_LIBRARY_HEADERS} 106 | ${CPP_WRAPPER_SOURCES_CORE} 107 | ${CPP_WRAPPER_SOURCES_PLUGIN} 108 | ${CPP_WRAPPER_SOURCES_APP} 109 | ) 110 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | void RegisterPlugins(flutter::PluginRegistry* registry) { 14 | AwesomeNotificationsPluginCApiRegisterWithRegistrar( 15 | registry->GetRegistrarForPlugin("AwesomeNotificationsPluginCApi")); 16 | FirebaseCorePluginCApiRegisterWithRegistrar( 17 | registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); 18 | FlutterSecureStorageWindowsPluginRegisterWithRegistrar( 19 | registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); 20 | } 21 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | awesome_notifications 7 | firebase_core 8 | flutter_secure_storage_windows 9 | ) 10 | 11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 12 | ) 13 | 14 | set(PLUGIN_BUNDLED_LIBRARIES) 15 | 16 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 17 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 18 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 21 | endforeach(plugin) 22 | 23 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 26 | endforeach(ffi_plugin) 27 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.brizaldi" "\0" 93 | VALUE "FileDescription", "flutter_project_template_riverpod" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "flutter_project_template_riverpod" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2022 com.brizaldi. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "flutter_project_template_riverpod.exe" "\0" 98 | VALUE "ProductName", "flutter_project_template_riverpod" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"App Template", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brizaldi/flutter-project-template-riverpod/b6905d46814a135ac064ae085da47cab0be4350a/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length <= 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /windows/runner/win32_window.cpp: -------------------------------------------------------------------------------- 1 | #include "win32_window.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "resource.h" 7 | 8 | namespace { 9 | 10 | /// Window attribute that enables dark mode window decorations. 11 | /// 12 | /// Redefined in case the developer's machine has a Windows SDK older than 13 | /// version 10.0.22000.0. 14 | /// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute 15 | #ifndef DWMWA_USE_IMMERSIVE_DARK_MODE 16 | #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 17 | #endif 18 | 19 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; 20 | 21 | /// Registry key for app theme preference. 22 | /// 23 | /// A value of 0 indicates apps should use dark mode. A non-zero or missing 24 | /// value indicates apps should use light mode. 25 | constexpr const wchar_t kGetPreferredBrightnessRegKey[] = 26 | L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; 27 | constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; 28 | 29 | // The number of Win32Window objects that currently exist. 30 | static int g_active_window_count = 0; 31 | 32 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); 33 | 34 | // Scale helper to convert logical scaler values to physical using passed in 35 | // scale factor 36 | int Scale(int source, double scale_factor) { 37 | return static_cast(source * scale_factor); 38 | } 39 | 40 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. 41 | // This API is only needed for PerMonitor V1 awareness mode. 42 | void EnableFullDpiSupportIfAvailable(HWND hwnd) { 43 | HMODULE user32_module = LoadLibraryA("User32.dll"); 44 | if (!user32_module) { 45 | return; 46 | } 47 | auto enable_non_client_dpi_scaling = 48 | reinterpret_cast( 49 | GetProcAddress(user32_module, "EnableNonClientDpiScaling")); 50 | if (enable_non_client_dpi_scaling != nullptr) { 51 | enable_non_client_dpi_scaling(hwnd); 52 | } 53 | FreeLibrary(user32_module); 54 | } 55 | 56 | } // namespace 57 | 58 | // Manages the Win32Window's window class registration. 59 | class WindowClassRegistrar { 60 | public: 61 | ~WindowClassRegistrar() = default; 62 | 63 | // Returns the singleton registrar instance. 64 | static WindowClassRegistrar* GetInstance() { 65 | if (!instance_) { 66 | instance_ = new WindowClassRegistrar(); 67 | } 68 | return instance_; 69 | } 70 | 71 | // Returns the name of the window class, registering the class if it hasn't 72 | // previously been registered. 73 | const wchar_t* GetWindowClass(); 74 | 75 | // Unregisters the window class. Should only be called if there are no 76 | // instances of the window. 77 | void UnregisterWindowClass(); 78 | 79 | private: 80 | WindowClassRegistrar() = default; 81 | 82 | static WindowClassRegistrar* instance_; 83 | 84 | bool class_registered_ = false; 85 | }; 86 | 87 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; 88 | 89 | const wchar_t* WindowClassRegistrar::GetWindowClass() { 90 | if (!class_registered_) { 91 | WNDCLASS window_class{}; 92 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); 93 | window_class.lpszClassName = kWindowClassName; 94 | window_class.style = CS_HREDRAW | CS_VREDRAW; 95 | window_class.cbClsExtra = 0; 96 | window_class.cbWndExtra = 0; 97 | window_class.hInstance = GetModuleHandle(nullptr); 98 | window_class.hIcon = 99 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); 100 | window_class.hbrBackground = 0; 101 | window_class.lpszMenuName = nullptr; 102 | window_class.lpfnWndProc = Win32Window::WndProc; 103 | RegisterClass(&window_class); 104 | class_registered_ = true; 105 | } 106 | return kWindowClassName; 107 | } 108 | 109 | void WindowClassRegistrar::UnregisterWindowClass() { 110 | UnregisterClass(kWindowClassName, nullptr); 111 | class_registered_ = false; 112 | } 113 | 114 | Win32Window::Win32Window() { 115 | ++g_active_window_count; 116 | } 117 | 118 | Win32Window::~Win32Window() { 119 | --g_active_window_count; 120 | Destroy(); 121 | } 122 | 123 | bool Win32Window::Create(const std::wstring& title, 124 | const Point& origin, 125 | const Size& size) { 126 | Destroy(); 127 | 128 | const wchar_t* window_class = 129 | WindowClassRegistrar::GetInstance()->GetWindowClass(); 130 | 131 | const POINT target_point = {static_cast(origin.x), 132 | static_cast(origin.y)}; 133 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); 134 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); 135 | double scale_factor = dpi / 96.0; 136 | 137 | HWND window = CreateWindow( 138 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW, 139 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), 140 | Scale(size.width, scale_factor), Scale(size.height, scale_factor), 141 | nullptr, nullptr, GetModuleHandle(nullptr), this); 142 | 143 | if (!window) { 144 | return false; 145 | } 146 | 147 | UpdateTheme(window); 148 | 149 | return OnCreate(); 150 | } 151 | 152 | bool Win32Window::Show() { 153 | return ShowWindow(window_handle_, SW_SHOWNORMAL); 154 | } 155 | 156 | // static 157 | LRESULT CALLBACK Win32Window::WndProc(HWND const window, 158 | UINT const message, 159 | WPARAM const wparam, 160 | LPARAM const lparam) noexcept { 161 | if (message == WM_NCCREATE) { 162 | auto window_struct = reinterpret_cast(lparam); 163 | SetWindowLongPtr(window, GWLP_USERDATA, 164 | reinterpret_cast(window_struct->lpCreateParams)); 165 | 166 | auto that = static_cast(window_struct->lpCreateParams); 167 | EnableFullDpiSupportIfAvailable(window); 168 | that->window_handle_ = window; 169 | } else if (Win32Window* that = GetThisFromHandle(window)) { 170 | return that->MessageHandler(window, message, wparam, lparam); 171 | } 172 | 173 | return DefWindowProc(window, message, wparam, lparam); 174 | } 175 | 176 | LRESULT 177 | Win32Window::MessageHandler(HWND hwnd, 178 | UINT const message, 179 | WPARAM const wparam, 180 | LPARAM const lparam) noexcept { 181 | switch (message) { 182 | case WM_DESTROY: 183 | window_handle_ = nullptr; 184 | Destroy(); 185 | if (quit_on_close_) { 186 | PostQuitMessage(0); 187 | } 188 | return 0; 189 | 190 | case WM_DPICHANGED: { 191 | auto newRectSize = reinterpret_cast(lparam); 192 | LONG newWidth = newRectSize->right - newRectSize->left; 193 | LONG newHeight = newRectSize->bottom - newRectSize->top; 194 | 195 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, 196 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE); 197 | 198 | return 0; 199 | } 200 | case WM_SIZE: { 201 | RECT rect = GetClientArea(); 202 | if (child_content_ != nullptr) { 203 | // Size and position the child window. 204 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, 205 | rect.bottom - rect.top, TRUE); 206 | } 207 | return 0; 208 | } 209 | 210 | case WM_ACTIVATE: 211 | if (child_content_ != nullptr) { 212 | SetFocus(child_content_); 213 | } 214 | return 0; 215 | 216 | case WM_DWMCOLORIZATIONCOLORCHANGED: 217 | UpdateTheme(hwnd); 218 | return 0; 219 | } 220 | 221 | return DefWindowProc(window_handle_, message, wparam, lparam); 222 | } 223 | 224 | void Win32Window::Destroy() { 225 | OnDestroy(); 226 | 227 | if (window_handle_) { 228 | DestroyWindow(window_handle_); 229 | window_handle_ = nullptr; 230 | } 231 | if (g_active_window_count == 0) { 232 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); 233 | } 234 | } 235 | 236 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { 237 | return reinterpret_cast( 238 | GetWindowLongPtr(window, GWLP_USERDATA)); 239 | } 240 | 241 | void Win32Window::SetChildContent(HWND content) { 242 | child_content_ = content; 243 | SetParent(content, window_handle_); 244 | RECT frame = GetClientArea(); 245 | 246 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left, 247 | frame.bottom - frame.top, true); 248 | 249 | SetFocus(child_content_); 250 | } 251 | 252 | RECT Win32Window::GetClientArea() { 253 | RECT frame; 254 | GetClientRect(window_handle_, &frame); 255 | return frame; 256 | } 257 | 258 | HWND Win32Window::GetHandle() { 259 | return window_handle_; 260 | } 261 | 262 | void Win32Window::SetQuitOnClose(bool quit_on_close) { 263 | quit_on_close_ = quit_on_close; 264 | } 265 | 266 | bool Win32Window::OnCreate() { 267 | // No-op; provided for subclasses. 268 | return true; 269 | } 270 | 271 | void Win32Window::OnDestroy() { 272 | // No-op; provided for subclasses. 273 | } 274 | 275 | void Win32Window::UpdateTheme(HWND const window) { 276 | DWORD light_mode; 277 | DWORD light_mode_size = sizeof(light_mode); 278 | LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, 279 | kGetPreferredBrightnessRegValue, 280 | RRF_RT_REG_DWORD, nullptr, &light_mode, 281 | &light_mode_size); 282 | 283 | if (result == ERROR_SUCCESS) { 284 | BOOL enable_dark_mode = light_mode == 0; 285 | DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, 286 | &enable_dark_mode, sizeof(enable_dark_mode)); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates a win32 window with |title| that is positioned and sized using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | --------------------------------------------------------------------------------