├── .github └── workflows │ └── main.yml ├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── boilerplate │ │ │ │ └── boilerplate │ │ │ │ └── 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 ├── icons │ ├── icon_astronomy.svg │ └── icon_image.svg └── images │ └── image_dog.png ├── integration_test ├── cases │ └── app_route_test.dart └── robot_tester │ ├── app_robot.dart │ ├── home_robot.dart │ ├── intro_robot.dart │ └── robot_tester_base.dart ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── bootstrap.dart ├── configs │ └── app_config.dart ├── core │ ├── bloc_core │ │ ├── bloc_observer.dart │ │ ├── ui_status.dart │ │ └── ui_status.freezed.dart │ ├── dimens │ │ └── app_dimens.dart │ ├── exceptions │ │ └── api_exception.dart │ ├── keys │ │ └── app_keys.dart │ ├── spacings │ │ └── app_spacing.dart │ └── themes │ │ └── app_themes.dart ├── data │ └── repositories │ │ └── dog_image_random │ │ ├── local │ │ ├── dog_image_local_repository.dart │ │ └── dog_image_local_repository_impl.dart │ │ └── remote │ │ ├── dog_image_random_repository.dart │ │ └── dog_image_random_repository_impl.dart ├── features │ ├── app │ │ ├── bloc │ │ │ ├── app_bloc.dart │ │ │ ├── app_bloc.freezed.dart │ │ │ ├── app_event.dart │ │ │ └── app_state.dart │ │ └── view │ │ │ ├── app.dart │ │ │ └── app_director.dart │ ├── demo │ │ ├── bloc │ │ │ ├── demo_bloc.dart │ │ │ ├── demo_bloc.freezed.dart │ │ │ ├── demo_event.dart │ │ │ ├── demo_notification.dart │ │ │ └── demo_state.dart │ │ └── view │ │ │ ├── assets_page.dart │ │ │ └── images_from_db_page.dart │ ├── dog_image_random │ │ ├── bloc │ │ │ ├── dog_image_random_bloc.dart │ │ │ ├── dog_image_random_bloc.freezed.dart │ │ │ ├── dog_image_random_event.dart │ │ │ ├── dog_image_random_notification.dart │ │ │ └── dog_image_random_state.dart │ │ └── view │ │ │ └── dog_image_random_page.dart │ ├── home │ │ └── home_page.dart │ ├── intro │ │ └── intro_page.dart │ └── setting │ │ └── setting_page.dart ├── injector │ ├── injector.dart │ └── modules │ │ ├── bloc_module.dart │ │ ├── database_module.dart │ │ ├── dio_module.dart │ │ ├── repository_module.dart │ │ ├── rest_client_module.dart │ │ └── service_module.dart ├── l10n │ ├── intl_en.arb │ └── intl_vi.arb ├── main.dart ├── router │ └── app_router.dart ├── services │ ├── app_service │ │ ├── app_service.dart │ │ └── app_service_impl.dart │ ├── auth_service │ │ ├── auth_service.dart │ │ └── auth_service_impl.dart │ ├── crashlytics_service │ │ ├── crashlytics_service.dart │ │ └── firebase_crashlytics_service.dart │ ├── local_storage_service │ │ ├── local_storage_service.dart │ │ └── shared_preferences_service.dart │ └── log_service │ │ ├── debug_log_service.dart │ │ └── log_service.dart ├── utils │ └── mapper_utils.dart └── widgets │ ├── error_page.dart │ ├── loading_page.dart │ ├── splash_page.dart │ └── widget_example.dart ├── packages ├── local_database │ ├── .gitignore │ ├── .metadata │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── lib │ │ ├── local_database.dart │ │ └── src │ │ │ ├── app_database.dart │ │ │ ├── app_database.g.dart │ │ │ ├── app_database_manager.dart │ │ │ ├── dao │ │ │ ├── dao.dart │ │ │ └── dog_image_dao.dart │ │ │ └── entities │ │ │ ├── dog_image_entity.dart │ │ │ └── entities.dart │ ├── pubspec.yaml │ └── test │ │ └── local_database_test.dart └── rest_client │ ├── .gitignore │ ├── .metadata │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── analysis_options.yaml │ ├── lib │ ├── rest_client.dart │ └── src │ │ ├── clients │ │ ├── clients.dart │ │ └── dog_api │ │ │ ├── dog_api.dart │ │ │ └── dog_api.g.dart │ │ ├── converters │ │ └── color_converter.dart │ │ ├── models │ │ ├── dog_image │ │ │ ├── dog_image.dart │ │ │ ├── dog_image.freezed.dart │ │ │ └── dog_image.g.dart │ │ └── models.dart │ │ └── paging │ │ ├── paging.dart │ │ ├── paging.freezed.dart │ │ └── paging.g.dart │ ├── pubspec.yaml │ └── test │ └── rest_client_test.dart ├── pubspec.lock ├── pubspec.yaml ├── readme_attach └── architecture.png ├── test ├── dependencies │ ├── mock_dependencies.dart │ └── mock_dependencies.mocks.dart ├── features │ └── dog_image_random_bloc_test.dart └── widget_test.dart └── web ├── favicon.png ├── icons ├── Icon-192.png ├── Icon-512.png ├── Icon-maskable-192.png └── Icon-maskable-512.png ├── index.html └── manifest.json /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Flutter CI 2 | 3 | # This workflow is triggered on pull request to main branch. 4 | 5 | on: 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | # on: push # Default will running for every branch. 11 | 12 | jobs: 13 | build_pipeline: 14 | runs-on: macos-latest 15 | 16 | strategy: 17 | matrix: 18 | api-level: 19 | - 29 20 | 21 | steps: 22 | 23 | # Setup Java environment in order to build the Android app. 24 | - uses: actions/checkout@v1 25 | - uses: actions/setup-java@v1 26 | with: 27 | java-version: '12.x' 28 | 29 | # Setup the flutter environment. 30 | - uses: subosito/flutter-action@v1 31 | with: 32 | channel: 'stable' # 'dev', 'alpha', default to: 'stable' 33 | flutter-version: '3.10.1' # you can also specify exact version of flutter 34 | 35 | # Get flutter dependencies. 36 | - run: flutter pub get 37 | 38 | - run: flutter pub upgrade 39 | 40 | - run: flutter pub run intl_utils:generate 41 | 42 | - run: flutter pub run build_runner build --delete-conflicting-outputs 43 | 44 | - name: local_database 45 | working-directory: ./packages/local_database 46 | run: flutter pub get && flutter pub upgrade && flutter pub run build_runner build --delete-conflicting-outputs 47 | 48 | - name: rest_client 49 | working-directory: ./packages/rest_client 50 | run: flutter pub get && flutter pub upgrade && flutter pub run build_runner build --delete-conflicting-outputs 51 | 52 | - run: flutter analyze 53 | 54 | - run: flutter test 55 | 56 | 57 | - name: Run integration tests 58 | uses: reactivecircus/android-emulator-runner@v2 59 | with: 60 | api-level: ${{ matrix.api-level }} 61 | arch: x86_64 62 | profile: Nexus 6 63 | script: flutter test integration_test --verbose 64 | 65 | 66 | - run: flutter build apk 67 | 68 | # Upload generated apk to the artifacts. 69 | - uses: actions/upload-artifact@v1 70 | with: 71 | name: release-apk 72 | path: build/app/outputs/apk/release/app-release.apk 73 | -------------------------------------------------------------------------------- /.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/ 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 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | 46 | # Flutter gen code 47 | /lib/generated 48 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: eb6d86ee27deecba4a83536aa20f366a6044895c 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: eb6d86ee27deecba4a83536aa20f366a6044895c 17 | base_revision: eb6d86ee27deecba4a83536aa20f366a6044895c 18 | - platform: android 19 | create_revision: eb6d86ee27deecba4a83536aa20f366a6044895c 20 | base_revision: eb6d86ee27deecba4a83536aa20f366a6044895c 21 | - platform: ios 22 | create_revision: eb6d86ee27deecba4a83536aa20f366a6044895c 23 | base_revision: eb6d86ee27deecba4a83536aa20f366a6044895c 24 | - platform: web 25 | create_revision: eb6d86ee27deecba4a83536aa20f366a6044895c 26 | base_revision: eb6d86ee27deecba4a83536aa20f366a6044895c 27 | 28 | # User provided section 29 | 30 | # List of Local paths (relative to this file) that should be 31 | # ignored by the migrate tool. 32 | # 33 | # Files that are not part of the templates will be ignored by default. 34 | unmanaged_files: 35 | - 'lib/main.dart' 36 | - 'ios/Runner.xcodeproj/project.pbxproj' 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Trương Nhật Duy 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 |
2 | 3 | 4 | GitHub: zeref278 5 | 6 | 7 | Buy Me A Coffee 8 | 9 | 10 |
11 | 12 | # Flutter Boilerplate Project 13 | 14 | A boilerplate project created in flutter using Bloc, Retrofit. Depend on code generation. 15 | ## Features 16 | 17 | * State management and examples 18 | * Api integration and examples 19 | * Local database and examples 20 | * Code generation 21 | * Local storage 22 | * Logging 23 | * Routing 24 | * Dependency Injection 25 | * Crashlytics template 26 | * DarkTheme 27 | * Multi languages 28 | * Unit tests 29 | * Integration test 30 | * Clean architecture 31 | * Flutter CI 32 | 33 | Some packages: 34 | - [Freezed](https://pub.dev/packages/freezed) 35 | - [Flutter Bloc](https://pub.dev/packages/flutter_bloc) 36 | - [Flutter gen](https://pub.dev/packages/flutter_gen) 37 | - [Retrofit](https://pub.dev/packages/retrofit) 38 | - [Dio](https://pub.dev/packages/retrofit) 39 | - [Bloc test](https://pub.dev/packages/bloc_test) 40 | - [Mockito](https://pub.dev/packages/mockito) 41 | - [Go router](https://pub.dev/packages/go_router) 42 | - [Dependency Injection](https://github.com/fluttercommunity/get_it) 43 | - [Logger](https://pub.dev/packages/logger) 44 | - [Floor](https://pub.dev/packages/floor) 45 | - [SharedPreferences](https://pub.dev/packages/shared_preferences) 46 | 47 | 48 | ## Getting Started 49 | 50 | The Boilerplate contains the minimal implementation required to create a new library or project. The repository code is preloaded with some basic components like basic app architecture, app theme, constants and required dependencies to create a new project. By using boiler plate code as standard initializer, we can have same patterns in all the projects that will inherit it. This will also help in reducing setup & development time by allowing you to use same code pattern and avoid re-writing from scratch. 51 | 52 | ### Up-Coming Features: 53 | 54 | * Handle multi bloc event in the same time by bloc concurrency example 55 | * Load more infinite list using bloc example 56 | * Authentication template 57 | 58 | ## Architecture 59 | 60 | 61 | ## How to Use 62 | **Step 1:** 63 | 64 | Fork, download or clone this repo by using the link below: 65 | 66 | ``` 67 | https://github.com/zeref278/flutter_boilerplate.git 68 | ``` 69 | 70 | **Step 2:** 71 | Go to project root and execute the following command in terminal to get the required dependencies and generate languages, freezed, flutter gen: 72 | 73 | ```cmd 74 | flutter pub get 75 | flutter pub run intl_utils:generate 76 | flutter pub run build_runner build --delete-conflicting-outputs 77 | ``` 78 | 79 | **Step 3:** 80 | Go to `/packages/rest_client` and execute the following command in terminal to generate model and api client: 81 | 82 | ```cmd 83 | flutter pub get && flutter pub run build_runner build --delete-conflicting-outputs 84 | ``` 85 | 86 | **Whenever change freezed file, assets, api** 87 | 88 | Run command 89 | ```cmd 90 | flutter pub get && flutter pub run build_runner build --delete-conflicting-outputs 91 | ``` 92 | 93 | ## Folder structure 94 | ``` 95 | flutter_boilerplate/ 96 | |- assets/ (assets) 97 | |- lib/ 98 | |- configs/ (flavor config) 99 | |- core/ (bloc observer, theme,...) 100 | |- data/ (repository) 101 | |- features/ (features page) 102 | |- generated/ (code generation includes localization and assets generation) 103 | |- injector/ (dependencies injector) 104 | |- l10n/ (localization resources 105 | |- router/ (routing) 106 | |- services/ (app services) 107 | |- utils/ (app utils) 108 | |- packages/ 109 | |- rest_client/ (api client) 110 | |- local_database/ (local database) 111 | |- integration_test 112 | |- test/ 113 | |- dependencies/ (mock dependencies) 114 | |- features/ (bloc test features) 115 | 116 | ``` 117 | 118 | ## [Freezed](https://pub.dev/packages/freezed): 119 | ### Create a immutable Model with any features available 120 | - Define a `constructor` + the `properties` 121 | - Override `toString`, operator `==`, hashCode 122 | - Implement a `copyWith` method to clone the object 123 | - Handling `de/serialization` 124 | ### Example 125 | ```dart 126 | part 'dog_image.freezed.dart'; 127 | part 'dog_image.g.dart'; 128 | 129 | @Freezed(fromJson: true) 130 | class DogImage with _$DogImage { 131 | const factory DogImage({ 132 | required String message, 133 | required String status, 134 | }) = _DogImage; 135 | 136 | factory DogImage.fromJson(Map json) => 137 | _$DogImageFromJson(json); 138 | } 139 | ``` 140 | ### Implement 141 | ```dart 142 | final DogImage dogImage = DogImage.fromJson(json); 143 | /// 144 | final DogImage dogImage = dogImage.copyWith(status: 'failed'); 145 | /// Deep copy, equal operator ... 146 | ... 147 | ``` 148 | 149 | ## [Retrofit]((https://pub.dev/packages/retrofit)): 150 | ### Create a api client by code generation, you do not need to implement each request manually 151 | ### Example 152 | ```dart 153 | part 'dog_api.g.dart'; 154 | 155 | @RestApi() 156 | abstract class DogApiClient { 157 | factory DogApiClient(Dio dio, {String baseUrl}) = _DogApiClient; 158 | 159 | @GET('/breeds/image/random') 160 | Future getDogImageRandom(); 161 | } 162 | ``` 163 | Generate to 164 | ```dart 165 | /// 166 | @override 167 | Future getDogImageRandom() async { 168 | const _extra = {}; 169 | final queryParameters = {}; 170 | final _headers = {}; 171 | final _data = {}; 172 | final _result = 173 | await _dio.fetch>(_setStreamType(Options( 174 | method: 'GET', 175 | headers: _headers, 176 | extra: _extra, 177 | ) 178 | .compose( 179 | _dio.options, 180 | '/breeds/image/random', 181 | queryParameters: queryParameters, 182 | data: _data, 183 | ) 184 | .copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl))); 185 | final value = DogImage.fromJson(_result.data!); 186 | return value; 187 | } 188 | ``` 189 | And this api client will use the `baseUrl` from a Dio injector 190 | ```dart 191 | injector.registerLazySingleton( 192 | () { 193 | /// TODO: custom DIO here 194 | final Dio dio = Dio( 195 | BaseOptions( 196 | baseUrl: AppConfig.baseUrl, 197 | ), 198 | ); 199 | if (!kReleaseMode) { 200 | dio.interceptors.add( 201 | LogInterceptor( 202 | requestHeader: true, 203 | requestBody: true, 204 | responseHeader: true, 205 | responseBody: true, 206 | request: false, 207 | ), 208 | ); 209 | } 210 | return dio; 211 | }, 212 | instanceName: dioInstance, 213 | ); 214 | 215 | injector.registerFactory( 216 | () => DogApiClient( 217 | injector(instanceName: dioInstance), 218 | ), 219 | ); 220 | ``` 221 | 222 | ## Mockito and Bloc tests: 223 | If a bloc that you want to test have a required dependencies, you must add it into annotations `@GenerateMocks` in `/test/app_test/app_test.dart`: 224 | ```dart 225 | @GenerateMocks([ 226 | DogImageRandomRepository, 227 | LogService, 228 | 229 | /// TODO 230 | ]) 231 | void main() {} 232 | ``` 233 | Run the following command to generate a mock dependency 234 | ```cmd 235 | flutter pub run build_runner build --delete-conflicting-outputs 236 | ``` 237 | 238 | Write a test file: 239 | ```dart 240 | setUp(() { 241 | bloc = DogImageRandomBloc( 242 | dogImageRandomRepository: repository, 243 | logService: logService, 244 | ); 245 | }); 246 | 247 | group('test add event [DogImageRandomRandomRequested]', () { 248 | blocTest( 249 | 'emit state when success', 250 | setUp: () { 251 | when(repository.getDogImageRandom()) 252 | .thenAnswer((_) => Future.value(image)); 253 | }, 254 | build: () => bloc, 255 | act: (_) => bloc.add( 256 | const DogImageRandomRandomRequested(), 257 | ), 258 | expect: () => [ 259 | isA().having( 260 | (state) => state.status, 261 | 'status', 262 | UIStatus.loading, 263 | ), 264 | isA() 265 | .having( 266 | (state) => state.status, 267 | 'status', 268 | UIStatus.loadSuccess, 269 | ) 270 | .having( 271 | (state) => state.dogImage, 272 | 'image', 273 | image, 274 | ), 275 | ], 276 | ); 277 | 278 | blocTest( 279 | 'emit state when failed', 280 | setUp: () { 281 | when(repository.getDogImageRandom()).thenThrow(Exception('error')); 282 | }, 283 | build: () => bloc, 284 | seed: () => const DogImageRandomState(dogImage: image), 285 | act: (_) => bloc.add( 286 | const DogImageRandomRandomRequested(), 287 | ), 288 | expect: () => [ 289 | isA().having( 290 | (state) => state.status, 291 | 'status', 292 | UIStatus.loading, 293 | ), 294 | isA() 295 | .having( 296 | (state) => state.status, 297 | 'status', 298 | UIStatus.actionFailed, 299 | ) 300 | .having( 301 | (state) => state.dogImage, 302 | 'image', 303 | image, 304 | ), 305 | ], 306 | ); 307 | }); 308 | ``` 309 | 310 | ## If you want to understand architecture or any packages used in this project, you can create a discussion on github repo. 311 | ## And feel free to create a pull request ! 312 | 313 | 314 | 315 | 316 | 317 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | analyzer: 3 | exclude: 4 | - 'lib/**/*.freezed.dart' 5 | - 'lib/**/*.g.dart' 6 | - 'lib/generated/' 7 | linter: 8 | rules: 9 | - always_declare_return_types 10 | - always_put_required_named_parameters_first 11 | - always_use_package_imports 12 | - annotate_overrides 13 | - avoid_bool_literals_in_conditional_expressions 14 | - avoid_catching_errors 15 | - avoid_double_and_int_checks 16 | - avoid_dynamic_calls 17 | - avoid_empty_else 18 | - avoid_equals_and_hash_code_on_mutable_classes 19 | - avoid_escaping_inner_quotes 20 | - avoid_field_initializers_in_const_classes 21 | - avoid_final_parameters 22 | - avoid_function_literals_in_foreach_calls 23 | - avoid_init_to_null 24 | - avoid_js_rounded_ints 25 | - avoid_multiple_declarations_per_line 26 | - avoid_null_checks_in_equality_operators 27 | - avoid_positional_boolean_parameters 28 | - avoid_print 29 | - avoid_private_typedef_functions 30 | - avoid_redundant_argument_values 31 | - avoid_relative_lib_imports 32 | - avoid_renaming_method_parameters 33 | - avoid_return_types_on_setters 34 | - avoid_returning_null_for_void 35 | - avoid_returning_this 36 | - avoid_setters_without_getters 37 | - avoid_shadowing_type_parameters 38 | - avoid_single_cascade_in_expression_statements 39 | - avoid_slow_async_io 40 | - avoid_type_to_string 41 | - avoid_types_as_parameter_names 42 | - avoid_unnecessary_containers 43 | - avoid_unused_constructor_parameters 44 | - avoid_void_async 45 | - avoid_web_libraries_in_flutter 46 | - await_only_futures 47 | - camel_case_extensions 48 | - camel_case_types 49 | - cancel_subscriptions 50 | - cascade_invocations 51 | - cast_nullable_to_non_nullable 52 | - collection_methods_unrelated_type 53 | - combinators_ordering 54 | - comment_references 55 | - conditional_uri_does_not_exist 56 | - constant_identifier_names 57 | - control_flow_in_finally 58 | - curly_braces_in_flow_control_structures 59 | - dangling_library_doc_comments 60 | - depend_on_referenced_packages 61 | - deprecated_consistency 62 | - directives_ordering 63 | - empty_catches 64 | - empty_constructor_bodies 65 | - empty_statements 66 | - enable_null_safety 67 | - eol_at_end_of_file 68 | - exhaustive_cases 69 | - file_names 70 | - flutter_style_todos 71 | - hash_and_equals 72 | - implicit_call_tearoffs 73 | - implementation_imports 74 | - iterable_contains_unrelated_type 75 | - join_return_with_assignment 76 | - leading_newlines_in_multiline_strings 77 | - library_annotations 78 | - library_names 79 | - library_prefixes 80 | - library_private_types_in_public_api 81 | - lines_longer_than_80_chars 82 | - list_remove_unrelated_type 83 | - literal_only_boolean_expressions 84 | - missing_whitespace_between_adjacent_strings 85 | - no_adjacent_strings_in_list 86 | - no_default_cases 87 | - no_duplicate_case_values 88 | - no_leading_underscores_for_library_prefixes 89 | - no_leading_underscores_for_local_identifiers 90 | - no_logic_in_create_state 91 | - no_runtimeType_toString 92 | - non_constant_identifier_names 93 | - noop_primitive_operations 94 | - null_check_on_nullable_type_parameter 95 | - null_closures 96 | - only_throw_errors 97 | - overridden_fields 98 | - package_api_docs 99 | - package_names 100 | - package_prefixed_library_names 101 | - parameter_assignments 102 | - prefer_adjacent_string_concatenation 103 | - prefer_asserts_in_initializer_lists 104 | - prefer_asserts_with_message 105 | - prefer_collection_literals 106 | - prefer_conditional_assignment 107 | - prefer_const_constructors 108 | - prefer_const_constructors_in_immutables 109 | - prefer_const_declarations 110 | - prefer_const_literals_to_create_immutables 111 | - prefer_constructors_over_static_methods 112 | - prefer_contains 113 | - prefer_final_fields 114 | - prefer_final_in_for_each 115 | - prefer_final_locals 116 | - prefer_for_elements_to_map_fromIterable 117 | - prefer_function_declarations_over_variables 118 | - prefer_generic_function_type_aliases 119 | - prefer_if_elements_to_conditional_expressions 120 | - prefer_if_null_operators 121 | - prefer_initializing_formals 122 | - prefer_inlined_adds 123 | - prefer_int_literals 124 | - prefer_interpolation_to_compose_strings 125 | - prefer_is_empty 126 | - prefer_is_not_empty 127 | - prefer_is_not_operator 128 | - prefer_iterable_whereType 129 | - prefer_null_aware_method_calls 130 | - prefer_null_aware_operators 131 | - prefer_single_quotes 132 | - prefer_spread_collections 133 | - prefer_typing_uninitialized_variables 134 | - prefer_void_to_null 135 | - provide_deprecation_message 136 | - recursive_getters 137 | - require_trailing_commas 138 | - secure_pubspec_urls 139 | - sized_box_for_whitespace 140 | - sized_box_shrink_expand 141 | - slash_for_doc_comments 142 | - sort_child_properties_last 143 | - sort_constructors_first 144 | - sort_unnamed_constructors_first 145 | - test_types_in_equals 146 | - throw_in_finally 147 | - tighten_type_of_initializing_formals 148 | - type_annotate_public_apis 149 | - type_init_formals 150 | - unawaited_futures 151 | - unnecessary_await_in_return 152 | - unnecessary_brace_in_string_interps 153 | - unnecessary_const 154 | - unnecessary_constructor_name 155 | - unnecessary_getters_setters 156 | - unnecessary_lambdas 157 | - unnecessary_late 158 | - unnecessary_library_directive 159 | - unnecessary_new 160 | - unnecessary_null_aware_assignments 161 | - unnecessary_null_checks 162 | - unnecessary_null_in_if_null_operators 163 | - unnecessary_nullable_for_final_variable_declarations 164 | - unnecessary_overrides 165 | - unnecessary_parenthesis 166 | - unnecessary_raw_strings 167 | - unnecessary_statements 168 | - unnecessary_string_escapes 169 | - unnecessary_string_interpolations 170 | - unnecessary_this 171 | - unnecessary_to_list_in_spreads 172 | - unrelated_type_equality_checks 173 | - use_build_context_synchronously 174 | - use_colored_box 175 | - use_decorated_box 176 | - use_enums 177 | - use_full_hex_values_for_flutter_colors 178 | - use_function_type_syntax_for_parameters 179 | - use_if_null_to_convert_nulls_to_bools 180 | - use_is_even_rather_than_modulo 181 | - use_key_in_widget_constructors 182 | - use_late_for_private_fields_and_variables 183 | - use_named_constants 184 | - use_raw_strings 185 | - use_rethrow_when_possible 186 | - use_setters_to_change_properties 187 | - use_string_buffers 188 | - use_string_in_part_of_directives 189 | - use_super_parameters 190 | - use_test_throws_matchers 191 | - use_to_and_as_if_applicable 192 | - valid_regexps 193 | - void_checks 194 | 195 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.boilerplate.boilerplate" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 50 | minSdkVersion flutter.minSdkVersion 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | } 62 | } 63 | } 64 | 65 | flutter { 66 | source '../..' 67 | } 68 | 69 | dependencies { 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | } 72 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 16 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/boilerplate/boilerplate/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.boilerplate.boilerplate 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 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.4-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/icons/icon_astronomy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/icon_image.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/image_dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/assets/images/image_dog.png -------------------------------------------------------------------------------- /integration_test/cases/app_route_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/main.dart' as app; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:integration_test/integration_test.dart'; 4 | import '../robot_tester/app_robot.dart'; 5 | import '../robot_tester/home_robot.dart'; 6 | import '../robot_tester/intro_robot.dart'; 7 | 8 | void main() { 9 | IntroRobot introTester; 10 | HomeRobot homeTester; 11 | AppRobot appRobot; 12 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 13 | 14 | group('app', () { 15 | /// Case: show intro page first use app 16 | testWidgets('Show intro page first use app', (tester) async { 17 | await app.main(); 18 | appRobot = AppRobot(tester); 19 | introTester = IntroRobot(tester); 20 | homeTester = HomeRobot(tester); 21 | await introTester.pressStarted(); 22 | await tester.pumpAndSettle(const Duration(seconds: 1)); 23 | await homeTester.verifyIsHomePage(); 24 | }); 25 | 26 | /// Case: show intro page did not first use app 27 | testWidgets('Show home page did not first use app', (tester) async { 28 | appRobot = AppRobot(tester); 29 | introTester = IntroRobot(tester); 30 | homeTester = HomeRobot(tester); 31 | 32 | appRobot.restartApp(); 33 | await tester.pumpAndSettle(const Duration(seconds: 1)); 34 | await homeTester.verifyIsHomePage(); 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /integration_test/robot_tester/app_robot.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/features/app/view/app.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'robot_tester_base.dart'; 5 | 6 | class AppRobot extends RobotTesterBase { 7 | AppRobot(super.tester); 8 | void restartApp() { 9 | runApp( 10 | App( 11 | key: UniqueKey(), 12 | ), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /integration_test/robot_tester/home_robot.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:boilerplate/core/keys/app_keys.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | import 'robot_tester_base.dart'; 6 | 7 | class HomeRobot extends RobotTesterBase { 8 | HomeRobot(super.tester); 9 | 10 | FutureOr verifyIsHomePage() async { 11 | await tester.pumpAndSettle(); 12 | expect(find.byKey(const Key(WidgetKeys.homeScaffoldKey)), findsOneWidget); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /integration_test/robot_tester/intro_robot.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:boilerplate/core/keys/app_keys.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | import 'robot_tester_base.dart'; 7 | 8 | class IntroRobot extends RobotTesterBase { 9 | IntroRobot(super.tester); 10 | 11 | FutureOr pressStarted() async { 12 | await tester.pumpAndSettle(); 13 | final Finder startedButton = 14 | find.byKey(const Key(WidgetKeys.introStartedButtonKey)); 15 | await tester.ensureVisible(startedButton); 16 | await tester.tap(startedButton); 17 | await tester.pumpAndSettle(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /integration_test/robot_tester/robot_tester_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | abstract class RobotTesterBase { 4 | RobotTesterBase(this.tester); 5 | final WidgetTester tester; 6 | 7 | Future wait(int milliseconds) async { 8 | await Future.delayed(Duration(milliseconds: milliseconds)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /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? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - FMDB (2.7.5): 4 | - FMDB/standard (= 2.7.5) 5 | - FMDB/standard (2.7.5) 6 | - integration_test (0.0.1): 7 | - Flutter 8 | - shared_preferences_foundation (0.0.1): 9 | - Flutter 10 | - FlutterMacOS 11 | - sqflite (0.0.3): 12 | - Flutter 13 | - FMDB (>= 2.7.5) 14 | 15 | DEPENDENCIES: 16 | - Flutter (from `Flutter`) 17 | - integration_test (from `.symlinks/plugins/integration_test/ios`) 18 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) 19 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 20 | 21 | SPEC REPOS: 22 | trunk: 23 | - FMDB 24 | 25 | EXTERNAL SOURCES: 26 | Flutter: 27 | :path: Flutter 28 | integration_test: 29 | :path: ".symlinks/plugins/integration_test/ios" 30 | shared_preferences_foundation: 31 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin" 32 | sqflite: 33 | :path: ".symlinks/plugins/sqflite/ios" 34 | 35 | SPEC CHECKSUMS: 36 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 37 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 38 | integration_test: 13825b8a9334a850581300559b8839134b124670 39 | shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c 40 | sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a 41 | 42 | PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 43 | 44 | COCOAPODS: 1.12.1 45 | -------------------------------------------------------------------------------- /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/Runner.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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/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 | Boilerplate 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | boilerplate 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | CFBundleLocalizations 51 | 52 | vi 53 | en 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/bootstrap.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:boilerplate/core/bloc_core/bloc_observer.dart'; 3 | import 'package:boilerplate/features/app/view/app.dart'; 4 | import 'package:boilerplate/injector/injector.dart'; 5 | import 'package:boilerplate/services/crashlytics_service/crashlytics_service.dart'; 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_bloc/flutter_bloc.dart'; 9 | import 'package:logger/logger.dart'; 10 | 11 | Future bootstrap({ 12 | AsyncCallback? firebaseInitialization, 13 | AsyncCallback? flavorConfiguration, 14 | }) async { 15 | await runZonedGuarded(() async { 16 | WidgetsFlutterBinding.ensureInitialized(); 17 | 18 | await firebaseInitialization?.call(); 19 | Logger.level = Level.verbose; 20 | await flavorConfiguration?.call(); 21 | 22 | Injector.init(); 23 | 24 | await Injector.instance.allReady(); 25 | 26 | Bloc.observer = AppBlocObserver(); 27 | 28 | runApp(const App()); 29 | }, (error, stack) { 30 | Injector.instance().recordException(error, stack); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /lib/configs/app_config.dart: -------------------------------------------------------------------------------- 1 | class AppConfig { 2 | static String baseUrl = ''; 3 | 4 | static const String defaultLocale = 'en'; 5 | 6 | static void configDev() { 7 | baseUrl = 'https://dog.ceo/api'; 8 | } 9 | 10 | static void configTest() { 11 | // TODO(boilerplate): flavoring 12 | } 13 | 14 | static void configProduction() { 15 | // TODO(boilerplate): flavoring 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/core/bloc_core/bloc_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/injector/injector.dart'; 2 | import 'package:boilerplate/services/log_service/log_service.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | class AppBlocObserver extends BlocObserver { 6 | AppBlocObserver() { 7 | _logService = Injector.instance(); 8 | } 9 | 10 | late final LogService _logService; 11 | 12 | @override 13 | void onCreate(BlocBase bloc) { 14 | _logService.i('BLoC: ${bloc.runtimeType} created'); 15 | super.onCreate(bloc); 16 | } 17 | 18 | @override 19 | void onEvent(Bloc bloc, Object? event) { 20 | _logService.i('Event: ${event.runtimeType} added'); 21 | super.onEvent(bloc, event); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/core/bloc_core/ui_status.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'ui_status.freezed.dart'; 4 | 5 | /// Ui status: initial, loading, loadSuccess and loadFailed 6 | @Freezed(fromJson: false, toJson: false) 7 | class UIStatus with _$UIStatus { 8 | const factory UIStatus.initial() = UIInitial; 9 | 10 | const factory UIStatus.loading() = UILoading; 11 | 12 | const factory UIStatus.loadFailed({ 13 | required String message, 14 | }) = UILoadFailed; 15 | 16 | const factory UIStatus.loadSuccess({ 17 | String? message, 18 | }) = UILoadSuccess; 19 | } 20 | -------------------------------------------------------------------------------- /lib/core/dimens/app_dimens.dart: -------------------------------------------------------------------------------- 1 | class AppDimens { 2 | AppDimens._(); 3 | 4 | static const double basePadding = 8; 5 | 6 | // TODO(Boilerplate): implements 7 | } 8 | -------------------------------------------------------------------------------- /lib/core/exceptions/api_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | /// Custom exception from dio error 4 | class ApiException implements Exception { 5 | ApiException({ 6 | this.statusCode, 7 | this.code, 8 | this.message, 9 | this.data, 10 | }); 11 | 12 | factory ApiException.fromDioError(DioError error) { 13 | return ApiException( 14 | statusCode: error.response?.statusCode, 15 | message: error.error?.toString(), 16 | ); 17 | } 18 | 19 | ApiException.fromJson(Map json) { 20 | if (json['error'] != null) { 21 | final Map jsons = json['error'] as Map; 22 | code = jsons['code'] as String?; 23 | message = jsons['message'] as String?; 24 | if (jsons['data'] != null) { 25 | data = jsons['data'] as Map?; 26 | } 27 | } 28 | } 29 | 30 | int? statusCode; 31 | String? message; 32 | String? code; 33 | Map? data; 34 | 35 | @override 36 | String toString() { 37 | return message ?? 'Exception'; 38 | } 39 | } 40 | 41 | extension HandleExceptionExtensions on Future { 42 | Future get onApiError { 43 | return onError( 44 | (exception, stackTrace) { 45 | throw ApiException.fromDioError(exception as DioError); 46 | }, 47 | test: (exception) => exception is DioError, 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/core/keys/app_keys.dart: -------------------------------------------------------------------------------- 1 | class AppKeys { 2 | AppKeys._(); 3 | 4 | /// App service 5 | static const String darkModeKey = 'darkMode'; 6 | static const String localeKey = 'locale'; 7 | static const String isFirstUseKey = 'isFirstUse'; 8 | 9 | /// Auth service 10 | static const String accessTokenKey = 'accessToken'; 11 | static const String refreshTokenKey = 'refreshToken'; 12 | static const String tenantKey = 'tenant'; 13 | static const String lastLoginKey = 'lastLogin'; 14 | static const String lastUsername = 'username'; 15 | static const String lastEmail = 'email'; 16 | } 17 | 18 | class WidgetKeys { 19 | /// Widget keys 20 | static const String homeScaffoldKey = 'home.scaffold'; 21 | static const String introStartedButtonKey = 'intro.startedButton'; 22 | } 23 | -------------------------------------------------------------------------------- /lib/core/spacings/app_spacing.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppSpacing { 4 | AppSpacing._(); 5 | 6 | static const horizontalSpacing2 = SizedBox(width: 2); 7 | static const horizontalSpacing4 = SizedBox(width: 4); 8 | static const horizontalSpacing6 = SizedBox(width: 6); 9 | static const horizontalSpacing8 = SizedBox(width: 8); 10 | static const horizontalSpacing10 = SizedBox(width: 10); 11 | static const horizontalSpacing12 = SizedBox(width: 12); 12 | static const horizontalSpacing16 = SizedBox(width: 16); 13 | static const horizontalSpacing20 = SizedBox(width: 20); 14 | static const horizontalSpacing24 = SizedBox(width: 24); 15 | static const horizontalSpacing32 = SizedBox(width: 32); 16 | static const horizontalSpacing48 = SizedBox(width: 48); 17 | 18 | static const verticalSpacing2 = SizedBox(height: 2); 19 | static const verticalSpacing4 = SizedBox(height: 4); 20 | static const verticalSpacing6 = SizedBox(height: 6); 21 | static const verticalSpacing8 = SizedBox(height: 8); 22 | static const verticalSpacing10 = SizedBox(height: 10); 23 | static const verticalSpacing12 = SizedBox(height: 12); 24 | static const verticalSpacing16 = SizedBox(height: 16); 25 | static const verticalSpacing20 = SizedBox(height: 20); 26 | static const verticalSpacing24 = SizedBox(height: 24); 27 | static const verticalSpacing32 = SizedBox(height: 32); 28 | static const verticalSpacing48 = SizedBox(height: 48); 29 | } 30 | -------------------------------------------------------------------------------- /lib/core/themes/app_themes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppThemes { 4 | AppThemes._(); 5 | 6 | //Primary 7 | static const Color _lightPrimaryColor = Color(0xffffffff); 8 | static const Color _darkPrimaryColor = Color(0xFF1a222d); 9 | 10 | //Background 11 | static const Color _lightBackgroundColor = Color(0xffffffff); 12 | static const Color _darkBackgroundColor = Color(0xFF1a222d); 13 | 14 | //Text 15 | static const Color _lightTextColor = Color(0xff000000); 16 | static const Color _darkTextColor = Color(0xffffffff); 17 | 18 | //Icon 19 | static const Color _lightIconColor = Color(0xff000000); 20 | static const Color _darkIconColor = Color(0xffffffff); 21 | 22 | //Text themes 23 | static const TextTheme _lightTextTheme = TextTheme( 24 | displayLarge: TextStyle(fontSize: 96, color: _lightTextColor), 25 | displayMedium: TextStyle(fontSize: 60, color: _lightTextColor), 26 | displaySmall: TextStyle(fontSize: 48, color: _lightTextColor), 27 | headlineMedium: TextStyle(fontSize: 34, color: _lightTextColor), 28 | headlineSmall: TextStyle(fontSize: 24, color: _lightTextColor), 29 | titleLarge: TextStyle( 30 | fontSize: 20, 31 | color: _lightTextColor, 32 | fontWeight: FontWeight.w500, 33 | ), 34 | titleMedium: TextStyle(fontSize: 16, color: _lightTextColor), 35 | titleSmall: TextStyle( 36 | fontSize: 14, 37 | color: _lightTextColor, 38 | fontWeight: FontWeight.w500, 39 | ), 40 | bodyLarge: TextStyle(fontSize: 16, color: _lightTextColor), 41 | bodyMedium: TextStyle(fontSize: 14, color: _lightTextColor), 42 | labelLarge: TextStyle( 43 | fontSize: 14, 44 | color: _lightTextColor, 45 | fontWeight: FontWeight.w500, 46 | ), 47 | bodySmall: TextStyle(fontSize: 12, color: _lightTextColor), 48 | labelSmall: TextStyle(fontSize: 14, color: _lightTextColor), 49 | ); 50 | 51 | static const TextTheme _darkTextTheme = TextTheme( 52 | displayLarge: TextStyle(fontSize: 96, color: _darkTextColor), 53 | displayMedium: TextStyle(fontSize: 60, color: _darkTextColor), 54 | displaySmall: TextStyle(fontSize: 48, color: _darkTextColor), 55 | headlineMedium: TextStyle(fontSize: 34, color: _darkTextColor), 56 | headlineSmall: TextStyle(fontSize: 24, color: _darkTextColor), 57 | titleLarge: TextStyle( 58 | fontSize: 20, 59 | color: _darkTextColor, 60 | fontWeight: FontWeight.w500, 61 | ), 62 | titleMedium: TextStyle(fontSize: 16, color: _darkTextColor), 63 | titleSmall: TextStyle( 64 | fontSize: 14, 65 | color: _darkTextColor, 66 | fontWeight: FontWeight.w500, 67 | ), 68 | bodyLarge: TextStyle(fontSize: 16, color: _darkTextColor), 69 | bodyMedium: TextStyle(fontSize: 14, color: _darkTextColor), 70 | labelLarge: TextStyle( 71 | fontSize: 14, 72 | color: _darkTextColor, 73 | fontWeight: FontWeight.w500, 74 | ), 75 | bodySmall: TextStyle(fontSize: 12, color: _darkTextColor), 76 | labelSmall: TextStyle(fontSize: 14, color: _darkTextColor), 77 | ); 78 | 79 | ///Light theme 80 | static final ThemeData lightTheme = ThemeData( 81 | brightness: Brightness.light, 82 | primaryColor: _lightPrimaryColor, 83 | scaffoldBackgroundColor: _lightBackgroundColor, 84 | appBarTheme: AppBarTheme( 85 | color: _lightBackgroundColor, 86 | iconTheme: const IconThemeData(color: _lightIconColor), 87 | toolbarTextStyle: _lightTextTheme.bodyMedium, 88 | titleTextStyle: _lightTextTheme.titleLarge, 89 | ), 90 | iconTheme: const IconThemeData( 91 | color: _lightIconColor, 92 | ), 93 | textTheme: _lightTextTheme, 94 | dividerTheme: const DividerThemeData( 95 | color: Colors.grey, 96 | ), 97 | ); 98 | 99 | ///Dark theme 100 | static final ThemeData darkTheme = ThemeData( 101 | brightness: Brightness.dark, 102 | primaryColor: _darkPrimaryColor, 103 | scaffoldBackgroundColor: _darkBackgroundColor, 104 | appBarTheme: AppBarTheme( 105 | color: _darkBackgroundColor, 106 | iconTheme: const IconThemeData(color: _darkIconColor), 107 | toolbarTextStyle: _darkTextTheme.bodyMedium, 108 | titleTextStyle: _darkTextTheme.titleLarge, 109 | ), 110 | iconTheme: const IconThemeData( 111 | color: _darkIconColor, 112 | ), 113 | textTheme: _darkTextTheme, 114 | dividerTheme: const DividerThemeData( 115 | color: Colors.grey, 116 | ), 117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /lib/data/repositories/dog_image_random/local/dog_image_local_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:local_database/local_database.dart'; 2 | 3 | abstract class DogImageLocalRepository { 4 | /// Local 5 | Future insertDogImageDB(DogImageEntity dogImageEntity); 6 | 7 | Future> getDogImagesFromDB(); 8 | 9 | Future deleteDogImageDB(DogImageEntity dogImageEntity); 10 | } 11 | -------------------------------------------------------------------------------- /lib/data/repositories/dog_image_random/local/dog_image_local_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/data/repositories/dog_image_random/local/dog_image_local_repository.dart'; 2 | import 'package:local_database/local_database.dart'; 3 | 4 | class DogImageLocalRepositoryImpl implements DogImageLocalRepository { 5 | DogImageLocalRepositoryImpl({ 6 | required AppDatabaseManager appDatabaseManager, 7 | }) : _appDatabaseManager = appDatabaseManager; 8 | 9 | late final AppDatabaseManager _appDatabaseManager; 10 | 11 | /// Local 12 | @override 13 | Future insertDogImageDB(DogImageEntity dogImageEntity) async { 14 | final DogImageDao dao = await _appDatabaseManager.dogImageDao; 15 | await dao.insertDogImage(dogImageEntity); 16 | } 17 | 18 | @override 19 | Future> getDogImagesFromDB() async { 20 | final DogImageDao dao = await _appDatabaseManager.dogImageDao; 21 | return dao.findAllDogImages(); 22 | } 23 | 24 | @override 25 | Future deleteDogImageDB(DogImageEntity dogImageEntity) async { 26 | final DogImageDao dao = await _appDatabaseManager.dogImageDao; 27 | return dao.deleteDogImage(dogImageEntity); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/data/repositories/dog_image_random/remote/dog_image_random_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:rest_client/rest_client.dart'; 2 | 3 | abstract class DogImageRandomRepository { 4 | /// Remote 5 | Future getDogImageRandom(); 6 | } 7 | -------------------------------------------------------------------------------- /lib/data/repositories/dog_image_random/remote/dog_image_random_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/core/exceptions/api_exception.dart'; 2 | import 'package:boilerplate/data/repositories/dog_image_random/remote/dog_image_random_repository.dart'; 3 | import 'package:rest_client/rest_client.dart'; 4 | 5 | class DogImageRandomRepositoryImpl implements DogImageRandomRepository { 6 | DogImageRandomRepositoryImpl({ 7 | required DogApiClient dogApiClient, 8 | }) : _dogApiClient = dogApiClient; 9 | 10 | late final DogApiClient _dogApiClient; 11 | 12 | /// Remote 13 | @override 14 | Future getDogImageRandom() async { 15 | return _dogApiClient.getDogImageRandom().onApiError; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/features/app/bloc/app_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:boilerplate/configs/app_config.dart'; 4 | import 'package:boilerplate/core/bloc_core/ui_status.dart'; 5 | import 'package:boilerplate/generated/l10n.dart'; 6 | import 'package:boilerplate/services/app_service/app_service.dart'; 7 | import 'package:boilerplate/services/log_service/log_service.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_bloc/flutter_bloc.dart'; 10 | import 'package:freezed_annotation/freezed_annotation.dart'; 11 | 12 | part 'app_event.dart'; 13 | part 'app_state.dart'; 14 | part 'app_bloc.freezed.dart'; 15 | 16 | class AppBloc extends Bloc { 17 | AppBloc({ 18 | required AppService appService, 19 | required LogService logService, 20 | }) : super(const AppState()) { 21 | _appService = appService; 22 | _logService = logService; 23 | on<_Loaded>(_onLoaded); 24 | on<_DarkModeChanged>(_onDarkModeChanged); 25 | on<_LocaleChanged>(_onLocaleChanged); 26 | on<_DisableFirstUse>(_onDisableFirstUse); 27 | } 28 | 29 | late final AppService _appService; 30 | late final LogService _logService; 31 | 32 | FutureOr _onLoaded( 33 | _Loaded event, 34 | Emitter emit, 35 | ) { 36 | try { 37 | emit( 38 | state.copyWith( 39 | status: const UILoading(), 40 | ), 41 | ); 42 | 43 | final bool darkMode = _appService.isDarkMode; 44 | final bool isFirstUse = _appService.isFirstUse; 45 | final String locale = _appService.locale; 46 | 47 | emit( 48 | state.copyWith( 49 | status: const UILoadSuccess(), 50 | isDarkMode: darkMode, 51 | isFirstUse: isFirstUse, 52 | locale: locale, 53 | ), 54 | ); 55 | } catch (e, s) { 56 | _logService.e('AppBloc load failed', e, s); 57 | emit( 58 | state.copyWith( 59 | status: UILoadFailed(message: e.toString()), 60 | ), 61 | ); 62 | } 63 | } 64 | 65 | FutureOr _onDarkModeChanged( 66 | _DarkModeChanged event, 67 | Emitter emit, 68 | ) async { 69 | final bool isDarkMode = !state.isDarkMode; 70 | await _appService.setIsDarkMode(darkMode: isDarkMode); 71 | emit( 72 | state.copyWith( 73 | isDarkMode: isDarkMode, 74 | ), 75 | ); 76 | } 77 | 78 | FutureOr _onLocaleChanged( 79 | _LocaleChanged event, 80 | Emitter emit, 81 | ) async { 82 | if (state.locale != event.locale) { 83 | await S.load(Locale(event.locale)); 84 | 85 | await _appService.setLocale(locale: event.locale); 86 | 87 | emit( 88 | state.copyWith( 89 | locale: event.locale, 90 | ), 91 | ); 92 | } 93 | } 94 | 95 | FutureOr _onDisableFirstUse( 96 | _DisableFirstUse event, 97 | Emitter emit, 98 | ) async { 99 | if (state.isFirstUse) { 100 | await _appService.setIsFirstUse(isFirstUse: false); 101 | emit( 102 | state.copyWith( 103 | isFirstUse: false, 104 | ), 105 | ); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/features/app/bloc/app_event.dart: -------------------------------------------------------------------------------- 1 | part of 'app_bloc.dart'; 2 | 3 | @Freezed() 4 | class AppEvent with _$AppEvent { 5 | const factory AppEvent.loaded() = _Loaded; 6 | 7 | const factory AppEvent.localeChanged({ 8 | required String locale, 9 | }) = _LocaleChanged; 10 | 11 | const factory AppEvent.disableFirstUse() = _DisableFirstUse; 12 | 13 | const factory AppEvent.darkModeChanged() = _DarkModeChanged; 14 | } 15 | -------------------------------------------------------------------------------- /lib/features/app/bloc/app_state.dart: -------------------------------------------------------------------------------- 1 | part of 'app_bloc.dart'; 2 | 3 | @Freezed() 4 | class AppState with _$AppState { 5 | const factory AppState({ 6 | @Default(UIInitial()) UIStatus status, 7 | @Default(AppConfig.defaultLocale) String locale, 8 | @Default(false) bool isDarkMode, 9 | @Default(true) bool isFirstUse, 10 | }) = _AppState; 11 | } 12 | -------------------------------------------------------------------------------- /lib/features/app/view/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/core/bloc_core/ui_status.dart'; 2 | import 'package:boilerplate/core/themes/app_themes.dart'; 3 | import 'package:boilerplate/features/app/bloc/app_bloc.dart'; 4 | import 'package:boilerplate/generated/l10n.dart'; 5 | import 'package:boilerplate/injector/injector.dart'; 6 | import 'package:boilerplate/router/app_router.dart'; 7 | import 'package:boilerplate/widgets/splash_page.dart'; 8 | import 'package:flutter/cupertino.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_bloc/flutter_bloc.dart'; 11 | import 'package:flutter_localizations/flutter_localizations.dart'; 12 | 13 | class App extends StatefulWidget { 14 | const App({super.key}); 15 | 16 | @override 17 | State createState() => _AppState(); 18 | } 19 | 20 | class _AppState extends State { 21 | late final AppBloc _appBloc; 22 | 23 | @override 24 | void initState() { 25 | _appBloc = Injector.instance() 26 | ..add( 27 | const AppEvent.loaded(), 28 | ); 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return BlocProvider.value( 35 | value: _appBloc, 36 | child: BlocSelector( 37 | bloc: _appBloc, 38 | selector: (state) => state.status, 39 | builder: (context, appStatus) { 40 | return appStatus.when( 41 | initial: () => const SplashPage(), 42 | loading: () => const SplashPage(), 43 | loadFailed: (_) => const SizedBox(), 44 | loadSuccess: (_) => const _App(), 45 | ); 46 | }, 47 | ), 48 | ); 49 | } 50 | } 51 | 52 | class _App extends StatelessWidget { 53 | const _App(); 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | final String locale = context.select( 58 | (AppBloc value) => value.state.locale, 59 | ); 60 | 61 | final bool isDarkMode = context.select( 62 | (AppBloc value) => value.state.isDarkMode, 63 | ); 64 | 65 | return MaterialApp.router( 66 | localizationsDelegates: const [ 67 | AppLocalizationDelegate(), 68 | GlobalMaterialLocalizations.delegate, 69 | GlobalWidgetsLocalizations.delegate, 70 | GlobalCupertinoLocalizations.delegate, 71 | DefaultCupertinoLocalizations.delegate 72 | ], 73 | supportedLocales: const AppLocalizationDelegate().supportedLocales, 74 | locale: Locale(locale), 75 | themeMode: isDarkMode ? ThemeMode.dark : ThemeMode.light, 76 | theme: AppThemes.lightTheme, 77 | darkTheme: AppThemes.darkTheme, 78 | routerConfig: AppRouter.router, 79 | title: 'BoilerPlate', 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/features/app/view/app_director.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/features/app/bloc/app_bloc.dart'; 2 | import 'package:boilerplate/features/home/home_page.dart'; 3 | import 'package:boilerplate/features/intro/intro_page.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | 7 | class AppDirector extends StatelessWidget { 8 | const AppDirector({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return BlocBuilder( 13 | buildWhen: (prev, next) => prev.isFirstUse != next.isFirstUse, 14 | builder: (context, state) { 15 | final bool isFirstUse = state.isFirstUse; 16 | if (isFirstUse) { 17 | return const IntroPage(); 18 | } else { 19 | return const HomePage(); 20 | } 21 | }, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/features/demo/bloc/demo_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:boilerplate/core/bloc_core/ui_status.dart'; 3 | import 'package:boilerplate/data/repositories/dog_image_random/local/dog_image_local_repository.dart'; 4 | import 'package:boilerplate/generated/l10n.dart'; 5 | import 'package:boilerplate/services/log_service/log_service.dart'; 6 | import 'package:boilerplate/utils/mapper_utils.dart'; 7 | import 'package:flutter_bloc/flutter_bloc.dart'; 8 | import 'package:freezed_annotation/freezed_annotation.dart'; 9 | import 'package:local_database/local_database.dart'; 10 | import 'package:rest_client/rest_client.dart'; 11 | 12 | part 'demo_event.dart'; 13 | part 'demo_state.dart'; 14 | part 'demo_bloc.freezed.dart'; 15 | part 'demo_notification.dart'; 16 | 17 | class DemoBloc extends Bloc { 18 | DemoBloc({ 19 | required LogService logService, 20 | required DogImageLocalRepository dogImageRandomRepository, 21 | }) : super(const DemoState()) { 22 | _repository = dogImageRandomRepository; 23 | _logService = logService; 24 | on<_LoadImageFromDB>(_onImagesLoadFromDB); 25 | on<_DeleteImageFromDB>(_onDeleteImageFromDB); 26 | } 27 | 28 | late final DogImageLocalRepository _repository; 29 | late final LogService _logService; 30 | 31 | FutureOr _onImagesLoadFromDB( 32 | _LoadImageFromDB event, 33 | Emitter emit, 34 | ) async { 35 | try { 36 | emit( 37 | state.copyWith( 38 | status: const UILoading(), 39 | ), 40 | ); 41 | 42 | final List imageEntities = 43 | await _repository.getDogImagesFromDB(); 44 | 45 | final List images = 46 | imageEntities.map(MapperUtils.mapDogImageEntity).toList(); 47 | 48 | emit( 49 | state.copyWith( 50 | status: const UILoadSuccess(), 51 | images: images, 52 | ), 53 | ); 54 | } catch (e, s) { 55 | _logService.e('DemoLoadImageFromDB failed', e, s); 56 | emit( 57 | state.copyWith( 58 | status: UILoadFailed(message: e.toString()), 59 | ), 60 | ); 61 | } 62 | } 63 | 64 | FutureOr _onDeleteImageFromDB( 65 | _DeleteImageFromDB event, 66 | Emitter emit, 67 | ) async { 68 | try { 69 | emit( 70 | state.copyWith( 71 | isBusy: true, 72 | ), 73 | ); 74 | 75 | await _repository 76 | .deleteDogImageDB(MapperUtils.mapDogImage(event.dogImage)); 77 | 78 | final List images = List.from(state.images) 79 | ..removeWhere((element) => element.message == event.dogImage.message); 80 | 81 | emit( 82 | state.copyWith( 83 | notification: 84 | _NotificationInsertSuccess(message: S.current.delete_success), 85 | images: images, 86 | isBusy: false, 87 | ), 88 | ); 89 | } catch (e, s) { 90 | _logService.e('DemoLoadImageFromDB failed', e, s); 91 | emit( 92 | state.copyWith( 93 | notification: _NotificationInsertFailed(message: e.toString()), 94 | isBusy: false, 95 | ), 96 | ); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/features/demo/bloc/demo_event.dart: -------------------------------------------------------------------------------- 1 | part of 'demo_bloc.dart'; 2 | 3 | @Freezed() 4 | class DemoEvent with _$DemoEvent { 5 | const factory DemoEvent.loadImageFromDB() = _LoadImageFromDB; 6 | 7 | const factory DemoEvent.deleteImageFromDB({ 8 | required DogImage dogImage, 9 | }) = _DeleteImageFromDB; 10 | } 11 | -------------------------------------------------------------------------------- /lib/features/demo/bloc/demo_notification.dart: -------------------------------------------------------------------------------- 1 | part of 'demo_bloc.dart'; 2 | 3 | @Freezed(equal: false) 4 | class DemoNotification with _$DemoNotification { 5 | factory DemoNotification.insertSuccess({ 6 | required String message, 7 | }) = _NotificationInsertSuccess; 8 | 9 | factory DemoNotification.insertFailed({ 10 | required String message, 11 | }) = _NotificationInsertFailed; 12 | } 13 | -------------------------------------------------------------------------------- /lib/features/demo/bloc/demo_state.dart: -------------------------------------------------------------------------------- 1 | part of 'demo_bloc.dart'; 2 | 3 | @Freezed() 4 | class DemoState with _$DemoState { 5 | const factory DemoState({ 6 | @Default(UIInitial()) UIStatus status, 7 | DemoNotification? notification, 8 | @Default([]) List images, 9 | @Default(false) bool isBusy, 10 | }) = _DemoState; 11 | } 12 | -------------------------------------------------------------------------------- /lib/features/demo/view/assets_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/core/dimens/app_dimens.dart'; 2 | import 'package:boilerplate/core/spacings/app_spacing.dart'; 3 | import 'package:boilerplate/generated/assets.gen.dart'; 4 | import 'package:boilerplate/generated/l10n.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class AssetsPage extends StatelessWidget { 8 | const AssetsPage({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | appBar: AppBar( 14 | title: Text(S.current.assets), 15 | ), 16 | body: SingleChildScrollView( 17 | padding: const EdgeInsets.all(AppDimens.basePadding), 18 | child: Column( 19 | children: [ 20 | const Text('svg'), 21 | AppSpacing.verticalSpacing24, 22 | Assets.icons.iconAstronomy.svg(), 23 | AppSpacing.verticalSpacing24, 24 | const Text('png'), 25 | Assets.images.imageDog.image(), 26 | AppSpacing.verticalSpacing24, 27 | ], 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/features/demo/view/images_from_db_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:another_flushbar/flushbar.dart'; 2 | import 'package:boilerplate/core/spacings/app_spacing.dart'; 3 | import 'package:boilerplate/features/demo/bloc/demo_bloc.dart'; 4 | import 'package:boilerplate/generated/l10n.dart'; 5 | import 'package:boilerplate/injector/injector.dart'; 6 | import 'package:boilerplate/widgets/error_page.dart'; 7 | import 'package:boilerplate/widgets/loading_page.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_bloc/flutter_bloc.dart'; 10 | import 'package:rest_client/rest_client.dart'; 11 | 12 | class ImagesFromDbPage extends StatelessWidget { 13 | const ImagesFromDbPage({super.key}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return BlocProvider( 18 | create: (context) => Injector.instance() 19 | ..add( 20 | const DemoEvent.loadImageFromDB(), 21 | ), 22 | child: const Scaffold( 23 | appBar: _AppBar(), 24 | body: _Body(), 25 | ), 26 | ); 27 | } 28 | } 29 | 30 | class _AppBar extends StatelessWidget implements PreferredSizeWidget { 31 | const _AppBar(); 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return AppBar( 36 | title: Text(S.current.image_from_db), 37 | ); 38 | } 39 | 40 | @override 41 | Size get preferredSize => const Size.fromHeight(kToolbarHeight); 42 | } 43 | 44 | class _Body extends StatelessWidget { 45 | const _Body(); 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | return SafeArea( 50 | child: BlocConsumer( 51 | listenWhen: (prev, next) => prev.status != next.status, 52 | listener: (context, state) { 53 | state.notification?.when( 54 | insertSuccess: (message) { 55 | Flushbar( 56 | message: message, 57 | duration: const Duration(seconds: 1), 58 | backgroundColor: Colors.green, 59 | ).show(context); 60 | }, 61 | insertFailed: (message) { 62 | Flushbar( 63 | message: message, 64 | duration: const Duration(seconds: 1), 65 | backgroundColor: Colors.red, 66 | ).show(context); 67 | }, 68 | ); 69 | }, 70 | buildWhen: (prev, next) => 71 | prev.status != next.status || prev.isBusy != next.isBusy, 72 | builder: (context, state) { 73 | return state.status.when( 74 | initial: () { 75 | return const LoadingPage(); 76 | }, 77 | loading: () { 78 | return const LoadingPage(); 79 | }, 80 | loadFailed: (message) { 81 | return ErrorPage( 82 | content: message, 83 | ); 84 | }, 85 | loadSuccess: (message) { 86 | return Stack( 87 | children: [ 88 | _buildImages(state.images), 89 | if (state.isBusy) const LoadingPage(), 90 | ], 91 | ); 92 | }, 93 | ); 94 | }, 95 | ), 96 | ); 97 | } 98 | 99 | Widget _buildImages(List images) { 100 | return ListView.separated( 101 | itemBuilder: (context, index) => Stack( 102 | children: [ 103 | Image.network(images[index].message), 104 | Positioned( 105 | child: InkWell( 106 | child: const Icon( 107 | Icons.delete, 108 | color: Colors.red, 109 | ), 110 | onTap: () { 111 | context.read().add( 112 | DemoEvent.deleteImageFromDB( 113 | dogImage: images[index], 114 | ), 115 | ); 116 | }, 117 | ), 118 | ) 119 | ], 120 | ), 121 | separatorBuilder: (context, index) => AppSpacing.verticalSpacing6, 122 | itemCount: images.length, 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/features/dog_image_random/bloc/dog_image_random_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:bloc_concurrency/bloc_concurrency.dart'; 3 | import 'package:boilerplate/core/bloc_core/ui_status.dart'; 4 | import 'package:boilerplate/data/repositories/dog_image_random/local/dog_image_local_repository.dart'; 5 | import 'package:boilerplate/data/repositories/dog_image_random/remote/dog_image_random_repository.dart'; 6 | import 'package:boilerplate/services/log_service/log_service.dart'; 7 | import 'package:boilerplate/utils/mapper_utils.dart'; 8 | import 'package:flutter/foundation.dart'; 9 | import 'package:flutter_bloc/flutter_bloc.dart'; 10 | import 'package:freezed_annotation/freezed_annotation.dart'; 11 | import 'package:local_database/local_database.dart'; 12 | import 'package:rest_client/rest_client.dart'; 13 | 14 | part 'dog_image_random_event.dart'; 15 | part 'dog_image_random_state.dart'; 16 | part 'dog_image_random_notification.dart'; 17 | part 'dog_image_random_bloc.freezed.dart'; 18 | 19 | class DogImageRandomBloc 20 | extends Bloc { 21 | DogImageRandomBloc({ 22 | required DogImageRandomRepository dogImageRandomRepository, 23 | required LogService logService, 24 | DogImageLocalRepository? dogImageLocalRepository, 25 | }) : super( 26 | const DogImageRandomState(), 27 | ) { 28 | _repository = dogImageRandomRepository; 29 | _localRepository = dogImageLocalRepository; 30 | _log = logService; 31 | on<_Loaded>(_onLoaded); 32 | on<_RandomRequested>(_onRandom, transformer: droppable()); 33 | } 34 | 35 | late final DogImageRandomRepository _repository; 36 | late final DogImageLocalRepository? _localRepository; 37 | late final LogService _log; 38 | 39 | FutureOr _onLoaded( 40 | _Loaded event, 41 | Emitter emit, 42 | ) { 43 | try { 44 | emit( 45 | state.copyWith( 46 | status: const UILoading(), 47 | ), 48 | ); 49 | 50 | emit( 51 | state.copyWith( 52 | status: const UILoadSuccess(), 53 | ), 54 | ); 55 | } catch (e, s) { 56 | _log.e('DogImageRandomLoaded failed', e, s); 57 | emit( 58 | state.copyWith( 59 | status: UILoadFailed(message: e.toString()), 60 | ), 61 | ); 62 | } 63 | } 64 | 65 | FutureOr _onRandom( 66 | _RandomRequested event, 67 | Emitter emit, 68 | ) async { 69 | try { 70 | emit( 71 | state.copyWith( 72 | isBusy: true, 73 | ), 74 | ); 75 | 76 | final DogImage image = await _repository.getDogImageRandom(); 77 | 78 | if (event.insertDb && !kIsWeb && _localRepository != null) { 79 | final DogImageEntity entity = MapperUtils.mapDogImage(image); 80 | await _localRepository!.insertDogImageDB(entity); 81 | } 82 | 83 | emit( 84 | state.copyWith( 85 | isBusy: false, 86 | status: const UILoadSuccess(), 87 | dogImage: image, 88 | ), 89 | ); 90 | } catch (e, s) { 91 | _log.e('DogImageRandomLoaded failed', e, s); 92 | emit( 93 | state.copyWith( 94 | isBusy: false, 95 | notification: _NotificationNotifyFailed( 96 | message: e.toString(), 97 | ), 98 | ), 99 | ); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/features/dog_image_random/bloc/dog_image_random_event.dart: -------------------------------------------------------------------------------- 1 | part of 'dog_image_random_bloc.dart'; 2 | 3 | @Freezed() 4 | class DogImageRandomEvent with _$DogImageRandomEvent { 5 | const factory DogImageRandomEvent.loaded() = _Loaded; 6 | const factory DogImageRandomEvent.randomRequested({ 7 | @Default(false) bool insertDb, 8 | }) = _RandomRequested; 9 | } 10 | -------------------------------------------------------------------------------- /lib/features/dog_image_random/bloc/dog_image_random_notification.dart: -------------------------------------------------------------------------------- 1 | part of 'dog_image_random_bloc.dart'; 2 | 3 | @Freezed(equal: false) 4 | class DogImageRandomNotification with _$DogImageRandomNotification { 5 | factory DogImageRandomNotification.notifySuccess({ 6 | required String message, 7 | }) = _NotificationNotifySuccess; 8 | 9 | factory DogImageRandomNotification.notifyFailed({ 10 | required String message, 11 | }) = _NotificationNotifyFailed; 12 | } 13 | -------------------------------------------------------------------------------- /lib/features/dog_image_random/bloc/dog_image_random_state.dart: -------------------------------------------------------------------------------- 1 | part of 'dog_image_random_bloc.dart'; 2 | 3 | @Freezed() 4 | class DogImageRandomState with _$DogImageRandomState { 5 | const factory DogImageRandomState({ 6 | @Default(UIInitial()) UIStatus status, 7 | DogImageRandomNotification? notification, 8 | @Default(DogImage(message: '', status: '')) DogImage dogImage, 9 | @Default(false) bool isBusy, 10 | }) = _DogImageRandomState; 11 | } 12 | -------------------------------------------------------------------------------- /lib/features/dog_image_random/view/dog_image_random_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:another_flushbar/flushbar.dart'; 2 | import 'package:boilerplate/core/dimens/app_dimens.dart'; 3 | import 'package:boilerplate/core/spacings/app_spacing.dart'; 4 | import 'package:boilerplate/features/dog_image_random/bloc/dog_image_random_bloc.dart'; 5 | import 'package:boilerplate/generated/l10n.dart'; 6 | import 'package:boilerplate/injector/injector.dart'; 7 | import 'package:boilerplate/widgets/error_page.dart'; 8 | import 'package:boilerplate/widgets/loading_page.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_bloc/flutter_bloc.dart'; 11 | 12 | class DogImageRandomPage extends StatefulWidget { 13 | const DogImageRandomPage({super.key}); 14 | 15 | @override 16 | State createState() => _DogImageRandomPageState(); 17 | } 18 | 19 | class _DogImageRandomPageState extends State { 20 | @override 21 | Widget build(BuildContext context) { 22 | return BlocProvider( 23 | create: (context) => Injector.instance(), 24 | child: const Scaffold( 25 | appBar: _AppBar(), 26 | body: _Body(), 27 | bottomNavigationBar: _ButtonBar(), 28 | ), 29 | ); 30 | } 31 | } 32 | 33 | class _AppBar extends StatelessWidget implements PreferredSizeWidget { 34 | const _AppBar(); 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return AppBar( 39 | title: Text(S.current.dog_image_random), 40 | ); 41 | } 42 | 43 | @override 44 | Size get preferredSize => const Size.fromHeight(kToolbarHeight); 45 | } 46 | 47 | class _Body extends StatelessWidget { 48 | const _Body(); 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return Center( 53 | child: BlocConsumer( 54 | listenWhen: (prev, next) => prev.notification != next.notification, 55 | listener: (context, state) { 56 | state.notification?.when( 57 | notifySuccess: (message) { 58 | Flushbar( 59 | message: message, 60 | duration: const Duration(seconds: 1), 61 | backgroundColor: Colors.green, 62 | ).show(context); 63 | }, 64 | notifyFailed: (message) { 65 | Flushbar( 66 | message: message, 67 | duration: const Duration(seconds: 1), 68 | backgroundColor: Colors.red, 69 | ).show(context); 70 | }, 71 | ); 72 | }, 73 | buildWhen: (prev, next) => 74 | prev.status != next.status || prev.isBusy != next.isBusy, 75 | builder: (context, state) { 76 | return Stack( 77 | alignment: Alignment.center, 78 | children: [ 79 | state.status.when( 80 | initial: () { 81 | return Text(S.current.press_button); 82 | }, 83 | loading: () { 84 | return const LoadingPage(); 85 | }, 86 | loadFailed: (message) { 87 | return ErrorPage( 88 | content: message, 89 | ); 90 | }, 91 | loadSuccess: (message) { 92 | return Image.network(state.dogImage.message); 93 | }, 94 | ), 95 | if (state.isBusy) const LoadingPage(), 96 | ], 97 | ); 98 | }, 99 | ), 100 | ); 101 | } 102 | } 103 | 104 | class _ButtonBar extends StatelessWidget { 105 | const _ButtonBar(); 106 | 107 | @override 108 | Widget build(BuildContext context) { 109 | return SafeArea( 110 | child: Container( 111 | padding: const EdgeInsets.symmetric( 112 | horizontal: AppDimens.basePadding, 113 | vertical: AppDimens.basePadding, 114 | ), 115 | child: Row( 116 | children: [ 117 | Expanded( 118 | child: ElevatedButton( 119 | child: Text(S.current.load_image), 120 | onPressed: () { 121 | context 122 | .read() 123 | .add(const DogImageRandomEvent.randomRequested()); 124 | }, 125 | ), 126 | ), 127 | AppSpacing.horizontalSpacing16, 128 | Expanded( 129 | child: ElevatedButton( 130 | child: Text(S.current.load_and_insert_db), 131 | onPressed: () { 132 | context.read().add( 133 | const DogImageRandomEvent.randomRequested( 134 | insertDb: true, 135 | ), 136 | ); 137 | }, 138 | ), 139 | ), 140 | ], 141 | ), 142 | ), 143 | ); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /lib/features/home/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/core/keys/app_keys.dart'; 2 | import 'package:boilerplate/core/spacings/app_spacing.dart'; 3 | import 'package:boilerplate/generated/l10n.dart'; 4 | import 'package:boilerplate/router/app_router.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:go_router/go_router.dart'; 7 | 8 | class HomePage extends StatelessWidget { 9 | const HomePage({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | key: const Key(WidgetKeys.homeScaffoldKey), 15 | appBar: AppBar( 16 | title: Text(S.of(context).home), 17 | ), 18 | body: Center( 19 | child: Column( 20 | children: [ 21 | ElevatedButton( 22 | child: Text(S.of(context).dog_image_random), 23 | onPressed: () { 24 | context.push(AppRouter.dogImageRandomPath); 25 | }, 26 | ), 27 | AppSpacing.verticalSpacing32, 28 | ElevatedButton( 29 | child: Text(S.of(context).setting), 30 | onPressed: () { 31 | context.push(AppRouter.settingPath); 32 | }, 33 | ), 34 | AppSpacing.verticalSpacing32, 35 | ElevatedButton( 36 | child: Text(S.of(context).assets), 37 | onPressed: () { 38 | context.push(AppRouter.assetsPath); 39 | }, 40 | ), 41 | AppSpacing.verticalSpacing32, 42 | ElevatedButton( 43 | child: Text(S.of(context).image_from_db), 44 | onPressed: () { 45 | context.push(AppRouter.imagesFromDbPath); 46 | }, 47 | ), 48 | ], 49 | ), 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/features/intro/intro_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/core/keys/app_keys.dart'; 2 | import 'package:boilerplate/core/spacings/app_spacing.dart'; 3 | import 'package:boilerplate/features/app/bloc/app_bloc.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | 7 | class IntroPage extends StatelessWidget { 8 | const IntroPage({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | body: Center( 14 | child: Column( 15 | mainAxisAlignment: MainAxisAlignment.center, 16 | children: [ 17 | const Text('Intro'), 18 | AppSpacing.verticalSpacing32, 19 | ElevatedButton( 20 | key: const Key(WidgetKeys.introStartedButtonKey), 21 | onPressed: () { 22 | context.read().add(const AppEvent.disableFirstUse()); 23 | }, 24 | child: const Text('Started'), 25 | ), 26 | ], 27 | ), 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/features/setting/setting_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/core/dimens/app_dimens.dart'; 2 | import 'package:boilerplate/core/spacings/app_spacing.dart'; 3 | import 'package:boilerplate/features/app/bloc/app_bloc.dart'; 4 | import 'package:boilerplate/generated/l10n.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_bloc/flutter_bloc.dart'; 7 | 8 | class SettingPage extends StatelessWidget { 9 | const SettingPage({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | appBar: AppBar( 15 | title: Text(S.of(context).setting), 16 | ), 17 | body: const Padding( 18 | padding: EdgeInsets.all(AppDimens.basePadding), 19 | child: Column( 20 | children: [ 21 | _LangRow(), 22 | AppSpacing.verticalSpacing24, 23 | _DarkModeRow(), 24 | ], 25 | ), 26 | ), 27 | ); 28 | } 29 | } 30 | 31 | class _LangRow extends StatelessWidget { 32 | const _LangRow(); 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | final String locale = context.select((AppBloc bloc) => bloc.state.locale); 37 | return Column( 38 | children: [ 39 | RadioListTile( 40 | value: 'en', 41 | groupValue: locale, 42 | onChanged: (value) { 43 | context 44 | .read() 45 | .add(const AppEvent.localeChanged(locale: 'en')); 46 | }, 47 | title: Text(S.current.english), 48 | ), 49 | RadioListTile( 50 | value: 'vi', 51 | groupValue: locale, 52 | onChanged: (value) { 53 | context 54 | .read() 55 | .add(const AppEvent.localeChanged(locale: 'vi')); 56 | }, 57 | title: Text(S.current.vietnamese), 58 | ), 59 | ], 60 | ); 61 | } 62 | } 63 | 64 | class _DarkModeRow extends StatelessWidget { 65 | const _DarkModeRow(); 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | final bool darkMode = 70 | context.select((AppBloc bloc) => bloc.state.isDarkMode); 71 | return SwitchListTile( 72 | value: darkMode, 73 | onChanged: (value) { 74 | context.read().add(const AppEvent.darkModeChanged()); 75 | }, 76 | title: Text(S.of(context).dark_mode), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/injector/injector.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/injector/modules/bloc_module.dart'; 2 | import 'package:boilerplate/injector/modules/database_module.dart'; 3 | import 'package:boilerplate/injector/modules/dio_module.dart'; 4 | import 'package:boilerplate/injector/modules/repository_module.dart'; 5 | import 'package:boilerplate/injector/modules/rest_client_module.dart'; 6 | import 'package:boilerplate/injector/modules/service_module.dart'; 7 | import 'package:flutter/foundation.dart'; 8 | import 'package:get_it/get_it.dart'; 9 | 10 | class Injector { 11 | Injector._(); 12 | static GetIt instance = GetIt.instance; 13 | 14 | static void init() { 15 | DioModule.setup(); 16 | ServiceModule.init(); 17 | RestClientModule.init(); 18 | 19 | if (!kIsWeb) { 20 | DatabaseModule.init(); 21 | } 22 | 23 | RepositoryModule.init(); 24 | BlocModule.init(); 25 | } 26 | 27 | static void reset() { 28 | instance.reset(); 29 | } 30 | 31 | static void resetLazySingleton() { 32 | instance.resetLazySingleton(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/injector/modules/bloc_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/features/app/bloc/app_bloc.dart'; 2 | import 'package:boilerplate/features/demo/bloc/demo_bloc.dart'; 3 | import 'package:boilerplate/features/dog_image_random/bloc/dog_image_random_bloc.dart'; 4 | import 'package:boilerplate/injector/injector.dart'; 5 | import 'package:flutter/foundation.dart'; 6 | 7 | class BlocModule { 8 | BlocModule._(); 9 | 10 | static void init() { 11 | final injector = Injector.instance; 12 | 13 | injector 14 | ..registerLazySingleton( 15 | () => AppBloc( 16 | appService: injector(), 17 | logService: injector(), 18 | ), 19 | ) 20 | ..registerFactory( 21 | () => DogImageRandomBloc( 22 | dogImageRandomRepository: injector(), 23 | dogImageLocalRepository: kIsWeb ? null : injector(), 24 | logService: injector(), 25 | ), 26 | ) 27 | ..registerFactory( 28 | () => DemoBloc( 29 | dogImageRandomRepository: injector(), 30 | logService: injector(), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/injector/modules/database_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/data/repositories/dog_image_random/local/dog_image_local_repository.dart'; 2 | import 'package:boilerplate/data/repositories/dog_image_random/local/dog_image_local_repository_impl.dart'; 3 | import 'package:boilerplate/injector/injector.dart'; 4 | import 'package:local_database/local_database.dart'; 5 | 6 | class DatabaseModule { 7 | DatabaseModule._(); 8 | 9 | static final _injector = Injector.instance; 10 | 11 | static void init() { 12 | /* Floor package didn't support for Web platform 13 | If you run this repo on web, you must remove Local database module, or using 14 | another local database package, or check kIsWeb everywhere you use the local 15 | database module 16 | */ 17 | 18 | _injector.registerSingletonAsync(() async { 19 | final AppDatabaseManager databaseManager = AppDatabaseManager(); 20 | await databaseManager.createDatabase(); 21 | return databaseManager; 22 | }); 23 | 24 | _initRepositories(); 25 | } 26 | 27 | static void _initRepositories() { 28 | _injector.registerFactory( 29 | () => DogImageLocalRepositoryImpl( 30 | appDatabaseManager: _injector(), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/injector/modules/dio_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/configs/app_config.dart'; 2 | import 'package:boilerplate/injector/injector.dart'; 3 | import 'package:dio/dio.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:get_it/get_it.dart'; 6 | import 'package:pretty_dio_logger/pretty_dio_logger.dart'; 7 | 8 | class DioModule { 9 | DioModule._(); 10 | 11 | static const String dioInstanceName = 'dioInstance'; 12 | static final GetIt _injector = Injector.instance; 13 | 14 | static void setup() { 15 | _setupDio(); 16 | } 17 | 18 | static void _setupDio() { 19 | /// Dio 20 | _injector.registerLazySingleton( 21 | () { 22 | // TODO(boilerplate): custom DIO here 23 | final Dio dio = Dio( 24 | BaseOptions( 25 | baseUrl: AppConfig.baseUrl, 26 | ), 27 | ); 28 | if (!kReleaseMode) { 29 | dio.interceptors.add( 30 | PrettyDioLogger( 31 | requestHeader: true, 32 | requestBody: true, 33 | responseHeader: true, 34 | request: false, 35 | ), 36 | ); 37 | } 38 | return dio; 39 | }, 40 | instanceName: dioInstanceName, 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/injector/modules/repository_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/data/repositories/dog_image_random/remote/dog_image_random_repository.dart'; 2 | import 'package:boilerplate/data/repositories/dog_image_random/remote/dog_image_random_repository_impl.dart'; 3 | import 'package:boilerplate/injector/injector.dart'; 4 | 5 | class RepositoryModule { 6 | RepositoryModule._(); 7 | 8 | static void init() { 9 | final injector = Injector.instance; 10 | 11 | injector.registerFactory( 12 | () => DogImageRandomRepositoryImpl( 13 | dogApiClient: injector(), 14 | ), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/injector/modules/rest_client_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/injector/injector.dart'; 2 | import 'package:boilerplate/injector/modules/dio_module.dart'; 3 | import 'package:rest_client/rest_client.dart'; 4 | 5 | class RestClientModule { 6 | RestClientModule._(); 7 | 8 | static void init() { 9 | final injector = Injector.instance; 10 | 11 | injector.registerFactory( 12 | () => DogApiClient( 13 | injector(instanceName: DioModule.dioInstanceName), 14 | ), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/injector/modules/service_module.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/injector/injector.dart'; 2 | import 'package:boilerplate/services/app_service/app_service.dart'; 3 | import 'package:boilerplate/services/app_service/app_service_impl.dart'; 4 | import 'package:boilerplate/services/crashlytics_service/crashlytics_service.dart'; 5 | import 'package:boilerplate/services/crashlytics_service/firebase_crashlytics_service.dart'; 6 | import 'package:boilerplate/services/local_storage_service/local_storage_service.dart'; 7 | import 'package:boilerplate/services/local_storage_service/shared_preferences_service.dart'; 8 | import 'package:boilerplate/services/log_service/debug_log_service.dart'; 9 | import 'package:boilerplate/services/log_service/log_service.dart'; 10 | 11 | class ServiceModule { 12 | ServiceModule._(); 13 | 14 | static void init() { 15 | final injector = Injector.instance; 16 | 17 | injector 18 | ..registerSingletonAsync(() async { 19 | return FirebaseCrashlyticsService(); 20 | }) 21 | ..registerFactory(DebugLogService.new) 22 | ..registerSingleton( 23 | SharedPreferencesService( 24 | logService: injector(), 25 | ), 26 | signalsReady: true, 27 | ) 28 | ..registerSingleton( 29 | AppServiceImpl( 30 | localStorageService: injector(), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/l10n/intl_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "load_failed": "Load failed", 3 | "dog_image_random": "Dog image random", 4 | "load_image": "Load image", 5 | "home": "Trang chủ", 6 | "press_button": "Press button", 7 | "setting": "Setting", 8 | "english": "English", 9 | "vietnamese": "Vietnamese", 10 | "dark_mode": "Dark mode", 11 | "assets": "Assets gen", 12 | "load_and_insert_db": "Load and insert DB", 13 | "image_from_db": "Image from DB", 14 | "delete_success": "Delete success", 15 | "delete_failed": "Delete failed", 16 | "didnt_supported": "Floor didnt support" 17 | } -------------------------------------------------------------------------------- /lib/l10n/intl_vi.arb: -------------------------------------------------------------------------------- 1 | { 2 | "load_failed": "Tải thất bại", 3 | "dog_image_random": "Hình ảnh cún ngẫu nhiên", 4 | "load_image": "Tải hình ảnh", 5 | "home": "Trang chủ", 6 | "press_button": "Nhấn nút", 7 | "setting": "Cài đặt", 8 | "english": "Tiếng Anh", 9 | "vietnamese": "Tiếng Việt", 10 | "dark_mode": "Chế độ tối", 11 | "assets": "Gen họa tiết", 12 | "load_and_insert_db": "Tải và lưu DB", 13 | "image_from_db": "Hình ảnh từ DB", 14 | "delete_success": "Xoá thành công", 15 | "delete_failed": "Xoá không thành công", 16 | "didnt_supported": "Không hỗ trợ" 17 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/bootstrap.dart'; 2 | import 'package:boilerplate/configs/app_config.dart'; 3 | 4 | Future main() async { 5 | await bootstrap( 6 | firebaseInitialization: () async {}, 7 | flavorConfiguration: () async { 8 | AppConfig.configDev(); 9 | }, 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /lib/router/app_router.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/features/app/view/app_director.dart'; 2 | import 'package:boilerplate/features/demo/view/assets_page.dart'; 3 | import 'package:boilerplate/features/demo/view/images_from_db_page.dart'; 4 | import 'package:boilerplate/features/dog_image_random/view/dog_image_random_page.dart'; 5 | import 'package:boilerplate/features/home/home_page.dart'; 6 | import 'package:boilerplate/features/setting/setting_page.dart'; 7 | import 'package:boilerplate/generated/l10n.dart'; 8 | import 'package:boilerplate/widgets/error_page.dart'; 9 | import 'package:flutter/foundation.dart'; 10 | import 'package:go_router/go_router.dart'; 11 | 12 | class AppRouter { 13 | AppRouter._(); 14 | 15 | static const String appDirector = 'appDirector'; 16 | static const String appDirectorPath = '/'; 17 | 18 | static const String homeNamed = 'home'; 19 | static const String homePath = '/'; 20 | 21 | static const String settingNamed = 'setting'; 22 | static const String settingPath = '/setting'; 23 | 24 | static const String assetsNamed = 'assets'; 25 | static const String assetsPath = '/assets'; 26 | 27 | static const String dogImageRandomNamed = 'dogImageRandom'; 28 | static const String dogImageRandomPath = '/dogImageRandom'; 29 | 30 | static const String imagesFromDbNamed = 'imagesFromDb'; 31 | static const String imagesFromDbPath = '/imagesFromDb'; 32 | 33 | static GoRouter get router => _router; 34 | static final _router = GoRouter( 35 | routes: [ 36 | GoRoute( 37 | name: appDirector, 38 | path: appDirectorPath, 39 | builder: (context, state) { 40 | return const AppDirector(); 41 | }, 42 | ), 43 | GoRoute( 44 | name: homeNamed, 45 | path: homePath, 46 | builder: (context, state) => const HomePage(), 47 | ), 48 | GoRoute( 49 | name: settingNamed, 50 | path: settingPath, 51 | builder: (context, state) => const SettingPage(), 52 | ), 53 | GoRoute( 54 | name: assetsNamed, 55 | path: assetsPath, 56 | builder: (context, state) => const AssetsPage(), 57 | ), 58 | GoRoute( 59 | name: dogImageRandomNamed, 60 | path: dogImageRandomPath, 61 | builder: (context, state) => const DogImageRandomPage(), 62 | ), 63 | GoRoute( 64 | name: imagesFromDbNamed, 65 | path: imagesFromDbPath, 66 | builder: (context, state) { 67 | if (!kIsWeb) { 68 | return const ImagesFromDbPage(); 69 | } 70 | 71 | return ErrorPage( 72 | content: S.current.didnt_supported, 73 | ); 74 | }, 75 | ), 76 | ], 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /lib/services/app_service/app_service.dart: -------------------------------------------------------------------------------- 1 | abstract class AppService { 2 | String get locale; 3 | bool get isDarkMode; 4 | bool get isFirstUse; 5 | 6 | Future setLocale({ 7 | required String locale, 8 | }); 9 | 10 | Future setIsDarkMode({ 11 | required bool darkMode, 12 | }); 13 | 14 | Future setIsFirstUse({ 15 | required bool isFirstUse, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/services/app_service/app_service_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/configs/app_config.dart'; 2 | import 'package:boilerplate/core/keys/app_keys.dart'; 3 | import 'package:boilerplate/services/app_service/app_service.dart'; 4 | import 'package:boilerplate/services/local_storage_service/local_storage_service.dart'; 5 | 6 | class AppServiceImpl implements AppService { 7 | AppServiceImpl({ 8 | required LocalStorageService localStorageService, 9 | }) : _localStorageService = localStorageService; 10 | late final LocalStorageService _localStorageService; 11 | 12 | @override 13 | bool get isDarkMode => 14 | _localStorageService.getBool(key: AppKeys.darkModeKey) ?? false; 15 | 16 | @override 17 | bool get isFirstUse => 18 | _localStorageService.getBool(key: AppKeys.isFirstUseKey) ?? true; 19 | 20 | @override 21 | String get locale => 22 | _localStorageService.getString(key: AppKeys.localeKey) ?? 23 | AppConfig.defaultLocale; 24 | 25 | @override 26 | Future setIsDarkMode({required bool darkMode}) async { 27 | return _localStorageService.setValue( 28 | key: AppKeys.darkModeKey, 29 | value: darkMode, 30 | ); 31 | } 32 | 33 | @override 34 | Future setIsFirstUse({required bool isFirstUse}) async { 35 | return _localStorageService.setValue( 36 | key: AppKeys.isFirstUseKey, 37 | value: isFirstUse, 38 | ); 39 | } 40 | 41 | @override 42 | Future setLocale({required String locale}) async { 43 | return _localStorageService.setValue( 44 | key: AppKeys.localeKey, 45 | value: locale, 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/services/auth_service/auth_service.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/lib/services/auth_service/auth_service.dart -------------------------------------------------------------------------------- /lib/services/auth_service/auth_service_impl.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/lib/services/auth_service/auth_service_impl.dart -------------------------------------------------------------------------------- /lib/services/crashlytics_service/crashlytics_service.dart: -------------------------------------------------------------------------------- 1 | abstract class CrashlyticsService { 2 | Future init(); 3 | 4 | Future recordException(dynamic exception, StackTrace? stack); 5 | } 6 | -------------------------------------------------------------------------------- /lib/services/crashlytics_service/firebase_crashlytics_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/services/crashlytics_service/crashlytics_service.dart'; 2 | 3 | class FirebaseCrashlyticsService implements CrashlyticsService { 4 | @override 5 | Future init() { 6 | // TODO(boilerplate): implement init 7 | throw UnimplementedError(); 8 | } 9 | 10 | @override 11 | Future recordException(dynamic exception, StackTrace? stack) { 12 | // TODO(boilerplate): implement recordException 13 | throw UnimplementedError(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/services/local_storage_service/local_storage_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | abstract class LocalStorageService { 4 | FutureOr init(); 5 | 6 | FutureOr setValue({ 7 | required String key, 8 | required dynamic value, 9 | }); 10 | 11 | Object? getValue({ 12 | required String key, 13 | }); 14 | 15 | String? getString({ 16 | required String key, 17 | }); 18 | 19 | int? getInt({ 20 | required String key, 21 | }); 22 | 23 | double? getDouble({ 24 | required String key, 25 | }); 26 | 27 | bool? getBool({ 28 | required String key, 29 | }); 30 | 31 | List? getStringList({ 32 | required String key, 33 | }); 34 | 35 | FutureOr removeEntry({ 36 | required String key, 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /lib/services/local_storage_service/shared_preferences_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:boilerplate/injector/injector.dart'; 3 | import 'package:boilerplate/services/local_storage_service/local_storage_service.dart'; 4 | import 'package:boilerplate/services/log_service/log_service.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | 7 | class SharedPreferencesService implements LocalStorageService { 8 | SharedPreferencesService({ 9 | required LogService logService, 10 | }) { 11 | _logService = logService; 12 | init(); 13 | } 14 | late final SharedPreferences _pref; 15 | late final LogService _logService; 16 | 17 | @override 18 | FutureOr init() async { 19 | _pref = await SharedPreferences.getInstance(); 20 | Injector.instance.signalReady(this); 21 | } 22 | 23 | @override 24 | Object? getValue({ 25 | required String key, 26 | }) { 27 | return _pref.get(key); 28 | } 29 | 30 | @override 31 | FutureOr setValue({ 32 | required String key, 33 | required dynamic value, 34 | }) async { 35 | if (value is String) { 36 | await _pref.setString(key, value); 37 | } else if (value is int) { 38 | await _pref.setInt(key, value); 39 | } else if (value is double) { 40 | await _pref.setDouble(key, value); 41 | } else if (value is bool) { 42 | await _pref.setBool(key, value); 43 | } else if (value is List) { 44 | await _pref.setStringList(key, value); 45 | } else { 46 | await _pref.setString(key, value.toString()); 47 | _logService.w( 48 | 'SharedPreferences did not support this type,' 49 | ' will save to String by toString() function', 50 | ); 51 | } 52 | } 53 | 54 | @override 55 | bool? getBool({required String key}) { 56 | return _pref.getBool(key); 57 | } 58 | 59 | @override 60 | double? getDouble({required String key}) { 61 | return _pref.getDouble(key); 62 | } 63 | 64 | @override 65 | int? getInt({required String key}) { 66 | return _pref.getInt(key); 67 | } 68 | 69 | @override 70 | String? getString({required String key}) { 71 | return _pref.getString(key); 72 | } 73 | 74 | @override 75 | List? getStringList({required String key}) { 76 | return _pref.getStringList(key); 77 | } 78 | 79 | @override 80 | FutureOr removeEntry({ 81 | required String key, 82 | }) async { 83 | final bool result = await _pref.remove(key); 84 | return result; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/services/log_service/debug_log_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer' as developer; 2 | 3 | import 'package:boilerplate/services/log_service/log_service.dart'; 4 | import 'package:logger/logger.dart'; 5 | 6 | class DebugLogService implements LogService { 7 | DebugLogService({Logger? logger}) { 8 | _logger = logger ?? 9 | Logger( 10 | printer: PrefixPrinter( 11 | PrettyPrinter( 12 | methodCount: 0, 13 | errorMethodCount: 500, 14 | lineLength: 100, 15 | ), 16 | ), 17 | output: _MyConsoleOutput(), 18 | ); 19 | } 20 | late final Logger _logger; 21 | 22 | @override 23 | void e(String message, dynamic e, StackTrace? stack) { 24 | _logger.e(message, e, stack); 25 | } 26 | 27 | @override 28 | void i(String message) { 29 | _logger.i(message); 30 | } 31 | 32 | @override 33 | void w(String message, [dynamic e, StackTrace? stack]) { 34 | _logger.w(message, e, stack); 35 | } 36 | } 37 | 38 | class _MyConsoleOutput extends LogOutput { 39 | @override 40 | void output(OutputEvent event) { 41 | event.lines.forEach(developer.log); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/services/log_service/log_service.dart: -------------------------------------------------------------------------------- 1 | abstract class LogService { 2 | void e(String message, dynamic e, StackTrace? stack); 3 | void i(String message); 4 | void w(String message, [dynamic e, StackTrace? stack]); 5 | } 6 | -------------------------------------------------------------------------------- /lib/utils/mapper_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:local_database/local_database.dart'; 2 | import 'package:rest_client/rest_client.dart'; 3 | 4 | class MapperUtils { 5 | MapperUtils._(); 6 | 7 | static DogImageEntity mapDogImage(DogImage dogImage) { 8 | return DogImageEntity( 9 | dogImage.message, 10 | dogImage.status, 11 | ); 12 | } 13 | 14 | static DogImage mapDogImageEntity(DogImageEntity dogImageEntity) { 15 | return DogImage( 16 | message: dogImageEntity.message, 17 | status: dogImageEntity.status, 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/widgets/error_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ErrorPage extends StatelessWidget { 4 | const ErrorPage({ 5 | required this.content, 6 | super.key, 7 | }); 8 | 9 | final String content; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | body: Center(child: Text(content)), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/widgets/loading_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoadingPage extends StatelessWidget { 4 | const LoadingPage({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Material( 9 | color: const Color(0xFF000000).withOpacity(0.1), 10 | child: const Center( 11 | child: InkWell( 12 | child: SizedBox( 13 | width: 40, 14 | height: 40, 15 | child: CircularProgressIndicator(), 16 | ), 17 | ), 18 | ), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/widgets/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 Material( 9 | color: Colors.white, 10 | child: Directionality( 11 | textDirection: TextDirection.ltr, 12 | child: CircularProgressIndicator( 13 | color: Colors.blue, 14 | ), 15 | ), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/widgets/widget_example.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/lib/widgets/widget_example.dart -------------------------------------------------------------------------------- /packages/local_database/.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/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | -------------------------------------------------------------------------------- /packages/local_database/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: eb6d86ee27deecba4a83536aa20f366a6044895c 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /packages/local_database/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /packages/local_database/LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /packages/local_database/README.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | TODO: Put a short description of the package here that helps potential users 15 | know whether this package might be useful for them. 16 | 17 | ## Features 18 | 19 | TODO: List what your package can do. Maybe include images, gifs, or videos. 20 | 21 | ## Getting started 22 | 23 | TODO: List prerequisites and provide or point to information on how to 24 | start using the package. 25 | 26 | ## Usage 27 | 28 | TODO: Include short and useful examples for package users. Add longer examples 29 | to `/example` folder. 30 | 31 | ```dart 32 | const like = 'sample'; 33 | ``` 34 | 35 | ## Additional information 36 | 37 | TODO: Tell users more about the package: where to find more information, how to 38 | contribute to the package, how to file issues, what response they can expect 39 | from the package authors, and more. 40 | -------------------------------------------------------------------------------- /packages/local_database/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | analyzer: 4 | exclude: 5 | - 'lib/**/*.freezed.dart' 6 | - 'lib/**/*.g.dart' 7 | -------------------------------------------------------------------------------- /packages/local_database/lib/local_database.dart: -------------------------------------------------------------------------------- 1 | library local_database; 2 | 3 | export 'src/app_database_manager.dart'; 4 | export 'src/entities/entities.dart'; 5 | export 'src/dao/dao.dart'; 6 | -------------------------------------------------------------------------------- /packages/local_database/lib/src/app_database.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:floor/floor.dart'; 3 | import 'package:local_database/local_database.dart'; 4 | import 'package:sqflite/sqflite.dart' as sqflite; 5 | 6 | part 'app_database.g.dart'; 7 | 8 | @Database(version: 1, entities: [DogImageEntity]) 9 | abstract class AppDatabase extends FloorDatabase { 10 | DogImageDao get dogImageDao; 11 | } 12 | -------------------------------------------------------------------------------- /packages/local_database/lib/src/app_database.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'app_database.dart'; 4 | 5 | // ************************************************************************** 6 | // FloorGenerator 7 | // ************************************************************************** 8 | 9 | // ignore: avoid_classes_with_only_static_members 10 | class $FloorAppDatabase { 11 | /// Creates a database builder for a persistent database. 12 | /// Once a database is built, you should keep a reference to it and re-use it. 13 | static _$AppDatabaseBuilder databaseBuilder(String name) => 14 | _$AppDatabaseBuilder(name); 15 | 16 | /// Creates a database builder for an in memory database. 17 | /// Information stored in an in memory database disappears when the process is killed. 18 | /// Once a database is built, you should keep a reference to it and re-use it. 19 | static _$AppDatabaseBuilder inMemoryDatabaseBuilder() => 20 | _$AppDatabaseBuilder(null); 21 | } 22 | 23 | class _$AppDatabaseBuilder { 24 | _$AppDatabaseBuilder(this.name); 25 | 26 | final String? name; 27 | 28 | final List _migrations = []; 29 | 30 | Callback? _callback; 31 | 32 | /// Adds migrations to the builder. 33 | _$AppDatabaseBuilder addMigrations(List migrations) { 34 | _migrations.addAll(migrations); 35 | return this; 36 | } 37 | 38 | /// Adds a database [Callback] to the builder. 39 | _$AppDatabaseBuilder addCallback(Callback callback) { 40 | _callback = callback; 41 | return this; 42 | } 43 | 44 | /// Creates the database and initializes it. 45 | Future build() async { 46 | final path = name != null 47 | ? await sqfliteDatabaseFactory.getDatabasePath(name!) 48 | : ':memory:'; 49 | final database = _$AppDatabase(); 50 | database.database = await database.open( 51 | path, 52 | _migrations, 53 | _callback, 54 | ); 55 | return database; 56 | } 57 | } 58 | 59 | class _$AppDatabase extends AppDatabase { 60 | _$AppDatabase([StreamController? listener]) { 61 | changeListener = listener ?? StreamController.broadcast(); 62 | } 63 | 64 | DogImageDao? _dogImageDaoInstance; 65 | 66 | Future open( 67 | String path, 68 | List migrations, [ 69 | Callback? callback, 70 | ]) async { 71 | final databaseOptions = sqflite.OpenDatabaseOptions( 72 | version: 1, 73 | onConfigure: (database) async { 74 | await database.execute('PRAGMA foreign_keys = ON'); 75 | await callback?.onConfigure?.call(database); 76 | }, 77 | onOpen: (database) async { 78 | await callback?.onOpen?.call(database); 79 | }, 80 | onUpgrade: (database, startVersion, endVersion) async { 81 | await MigrationAdapter.runMigrations( 82 | database, startVersion, endVersion, migrations); 83 | 84 | await callback?.onUpgrade?.call(database, startVersion, endVersion); 85 | }, 86 | onCreate: (database, version) async { 87 | await database.execute( 88 | 'CREATE TABLE IF NOT EXISTS `DogImageEntity` (`message` TEXT NOT NULL, `status` TEXT NOT NULL, PRIMARY KEY (`message`))'); 89 | 90 | await callback?.onCreate?.call(database, version); 91 | }, 92 | ); 93 | return sqfliteDatabaseFactory.openDatabase(path, options: databaseOptions); 94 | } 95 | 96 | @override 97 | DogImageDao get dogImageDao { 98 | return _dogImageDaoInstance ??= _$DogImageDao(database, changeListener); 99 | } 100 | } 101 | 102 | class _$DogImageDao extends DogImageDao { 103 | _$DogImageDao( 104 | this.database, 105 | this.changeListener, 106 | ) : _queryAdapter = QueryAdapter(database), 107 | _dogImageEntityInsertionAdapter = InsertionAdapter( 108 | database, 109 | 'DogImageEntity', 110 | (DogImageEntity item) => { 111 | 'message': item.message, 112 | 'status': item.status 113 | }), 114 | _dogImageEntityDeletionAdapter = DeletionAdapter( 115 | database, 116 | 'DogImageEntity', 117 | ['message'], 118 | (DogImageEntity item) => { 119 | 'message': item.message, 120 | 'status': item.status 121 | }); 122 | 123 | final sqflite.DatabaseExecutor database; 124 | 125 | final StreamController changeListener; 126 | 127 | final QueryAdapter _queryAdapter; 128 | 129 | final InsertionAdapter _dogImageEntityInsertionAdapter; 130 | 131 | final DeletionAdapter _dogImageEntityDeletionAdapter; 132 | 133 | @override 134 | Future> findAllDogImages() async { 135 | return _queryAdapter.queryList('SELECT * FROM DogImageEntity', 136 | mapper: (Map row) => 137 | DogImageEntity(row['message'] as String, row['status'] as String)); 138 | } 139 | 140 | @override 141 | Future insertDogImage(DogImageEntity dogImageEntity) async { 142 | await _dogImageEntityInsertionAdapter.insert( 143 | dogImageEntity, OnConflictStrategy.abort); 144 | } 145 | 146 | @override 147 | Future deleteDogImage(DogImageEntity dogImageEntity) async { 148 | await _dogImageEntityDeletionAdapter.delete(dogImageEntity); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /packages/local_database/lib/src/app_database_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:local_database/local_database.dart'; 4 | 5 | import 'app_database.dart'; 6 | 7 | class AppDatabaseManager { 8 | static final AppDatabaseManager _singleton = AppDatabaseManager._internal(); 9 | 10 | factory AppDatabaseManager() { 11 | return _singleton; 12 | } 13 | 14 | AppDatabaseManager._internal(); 15 | 16 | late final AppDatabase _database; 17 | 18 | ///DAO 19 | DogImageDao? _dogImageDao; 20 | 21 | Future createDatabase() async { 22 | _database = await $FloorAppDatabase 23 | .databaseBuilder('app_database.db') 24 | .addMigrations( 25 | [ 26 | //_migration1to2, 27 | //... 28 | ], 29 | ).build(); 30 | initDao(); 31 | } 32 | 33 | void initDao() { 34 | _dogImageDao = _database.dogImageDao; 35 | } 36 | 37 | FutureOr get dogImageDao async { 38 | if (_dogImageDao == null) { 39 | await createDatabase(); 40 | } 41 | return _dogImageDao!; 42 | } 43 | 44 | // Migration (add database migration) 45 | 46 | // After release app to Store, whenever add entity, change entity ..., must write migraion 47 | // Step 1. Level up database version in app_database.dart (example 1 -> 2) 48 | // 49 | // Step 2. Write migration (example migration1to2) 50 | // final _migration1to2 = Migration(1, 2, (database) async { 51 | // await database.execute( 52 | // 'CREATE TABLE IF NOT EXISTS `NotificationDb` (`key` INTEGER, `data` TEXT, `pushType` INTEGER, `error` INTEGER, PRIMARY KEY (`key`))'); 53 | // await database.execute('ALTER TABLE OrderDB ADD listShortItem TEXT'); 54 | // }); 55 | // 56 | // Step 3. Run command "flutter clean && flutter pub get && flutter pub run build_runner build --delete-conflicting-outputs" 57 | 58 | } 59 | -------------------------------------------------------------------------------- /packages/local_database/lib/src/dao/dao.dart: -------------------------------------------------------------------------------- 1 | export 'dog_image_dao.dart'; 2 | -------------------------------------------------------------------------------- /packages/local_database/lib/src/dao/dog_image_dao.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | import 'package:local_database/src/entities/dog_image_entity.dart'; 3 | 4 | @dao 5 | abstract class DogImageDao { 6 | @Query('SELECT * FROM DogImageEntity') 7 | Future> findAllDogImages(); 8 | 9 | @insert 10 | Future insertDogImage(DogImageEntity dogImageEntity); 11 | 12 | @delete 13 | Future deleteDogImage(DogImageEntity dogImageEntity); 14 | } 15 | -------------------------------------------------------------------------------- /packages/local_database/lib/src/entities/dog_image_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:floor/floor.dart'; 2 | 3 | @entity 4 | class DogImageEntity { 5 | @primaryKey 6 | final String message; 7 | 8 | final String status; 9 | 10 | DogImageEntity(this.message, this.status); 11 | } 12 | -------------------------------------------------------------------------------- /packages/local_database/lib/src/entities/entities.dart: -------------------------------------------------------------------------------- 1 | export 'dog_image_entity.dart'; 2 | -------------------------------------------------------------------------------- /packages/local_database/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: local_database 2 | description: A new Flutter project. 3 | version: 0.0.1 4 | homepage: 5 | 6 | environment: 7 | sdk: '>=3.6.0 <4.0.0' 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | floor: ^1.5.0 13 | sqflite: ^2.4.1 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | flutter_lints: ^5.0.0 19 | floor_generator: ^1.5.0 20 | build_runner: ^2.4.15 21 | 22 | # For information on the generic Dart part of this file, see the 23 | # following page: https://dart.dev/tools/pub/pubspec 24 | 25 | # The following section is specific to Flutter packages. 26 | flutter: 27 | 28 | # To add assets to your package, add an assets section, like this: 29 | # assets: 30 | # - images/a_dot_burr.jpeg 31 | # - images/a_dot_ham.jpeg 32 | # 33 | # For details regarding assets in packages, see 34 | # https://flutter.dev/assets-and-images/#from-packages 35 | # 36 | # An image asset can refer to one or more resolution-specific "variants", see 37 | # https://flutter.dev/assets-and-images/#resolution-aware 38 | 39 | # To add custom fonts to your package, add a fonts section here, 40 | # in this "flutter" section. Each entry in this list should have a 41 | # "family" key with the font family name, and a "fonts" key with a 42 | # list giving the asset and other descriptors for the font. For 43 | # example: 44 | # fonts: 45 | # - family: Schyler 46 | # fonts: 47 | # - asset: fonts/Schyler-Regular.ttf 48 | # - asset: fonts/Schyler-Italic.ttf 49 | # style: italic 50 | # - family: Trajan Pro 51 | # fonts: 52 | # - asset: fonts/TrajanPro.ttf 53 | # - asset: fonts/TrajanPro_Bold.ttf 54 | # weight: 700 55 | # 56 | # For details regarding fonts in packages, see 57 | # https://flutter.dev/custom-fonts/#from-packages 58 | -------------------------------------------------------------------------------- /packages/local_database/test/local_database_test.dart: -------------------------------------------------------------------------------- 1 | void main() {} 2 | -------------------------------------------------------------------------------- /packages/rest_client/.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/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | -------------------------------------------------------------------------------- /packages/rest_client/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: eb6d86ee27deecba4a83536aa20f366a6044895c 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /packages/rest_client/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /packages/rest_client/LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /packages/rest_client/README.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | TODO: Put a short description of the package here that helps potential users 15 | know whether this package might be useful for them. 16 | 17 | ## Features 18 | 19 | TODO: List what your package can do. Maybe include images, gifs, or videos. 20 | 21 | ## Getting started 22 | 23 | TODO: List prerequisites and provide or point to information on how to 24 | start using the package. 25 | 26 | ## Usage 27 | 28 | TODO: Include short and useful examples for package users. Add longer examples 29 | to `/example` folder. 30 | 31 | ```dart 32 | const like = 'sample'; 33 | ``` 34 | 35 | ## Additional information 36 | 37 | TODO: Tell users more about the package: where to find more information, how to 38 | contribute to the package, how to file issues, what response they can expect 39 | from the package authors, and more. 40 | -------------------------------------------------------------------------------- /packages/rest_client/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | analyzer: 4 | exclude: 5 | - 'lib/**/*.freezed.dart' 6 | - 'lib/**/*.g.dart' 7 | linter: 8 | rules: 9 | - always_declare_return_types 10 | - always_put_required_named_parameters_first 11 | - always_use_package_imports 12 | - annotate_overrides 13 | - avoid_bool_literals_in_conditional_expressions 14 | - avoid_catching_errors 15 | - avoid_double_and_int_checks 16 | - avoid_dynamic_calls 17 | - avoid_empty_else 18 | - avoid_equals_and_hash_code_on_mutable_classes 19 | - avoid_escaping_inner_quotes 20 | - avoid_field_initializers_in_const_classes 21 | - avoid_final_parameters 22 | - avoid_function_literals_in_foreach_calls 23 | - avoid_init_to_null 24 | - avoid_js_rounded_ints 25 | - avoid_multiple_declarations_per_line 26 | - avoid_null_checks_in_equality_operators 27 | - avoid_positional_boolean_parameters 28 | - avoid_print 29 | - avoid_private_typedef_functions 30 | - avoid_redundant_argument_values 31 | - avoid_relative_lib_imports 32 | - avoid_renaming_method_parameters 33 | - avoid_return_types_on_setters 34 | - avoid_returning_null_for_void 35 | - avoid_returning_this 36 | - avoid_setters_without_getters 37 | - avoid_shadowing_type_parameters 38 | - avoid_single_cascade_in_expression_statements 39 | - avoid_slow_async_io 40 | - avoid_type_to_string 41 | - avoid_types_as_parameter_names 42 | - avoid_unnecessary_containers 43 | - avoid_unused_constructor_parameters 44 | - avoid_void_async 45 | - avoid_web_libraries_in_flutter 46 | - await_only_futures 47 | - camel_case_extensions 48 | - camel_case_types 49 | - cancel_subscriptions 50 | - cascade_invocations 51 | - cast_nullable_to_non_nullable 52 | - collection_methods_unrelated_type 53 | - combinators_ordering 54 | - comment_references 55 | - conditional_uri_does_not_exist 56 | - constant_identifier_names 57 | - control_flow_in_finally 58 | - curly_braces_in_flow_control_structures 59 | - dangling_library_doc_comments 60 | - depend_on_referenced_packages 61 | - deprecated_consistency 62 | - directives_ordering 63 | - empty_catches 64 | - empty_constructor_bodies 65 | - empty_statements 66 | - enable_null_safety 67 | - eol_at_end_of_file 68 | - exhaustive_cases 69 | - file_names 70 | - flutter_style_todos 71 | - hash_and_equals 72 | - implicit_call_tearoffs 73 | - implementation_imports 74 | - iterable_contains_unrelated_type 75 | - join_return_with_assignment 76 | - leading_newlines_in_multiline_strings 77 | - library_annotations 78 | - library_names 79 | - library_prefixes 80 | - library_private_types_in_public_api 81 | - lines_longer_than_80_chars 82 | - list_remove_unrelated_type 83 | - literal_only_boolean_expressions 84 | - missing_whitespace_between_adjacent_strings 85 | - no_adjacent_strings_in_list 86 | - no_default_cases 87 | - no_duplicate_case_values 88 | - no_leading_underscores_for_library_prefixes 89 | - no_leading_underscores_for_local_identifiers 90 | - no_logic_in_create_state 91 | - no_runtimeType_toString 92 | - non_constant_identifier_names 93 | - noop_primitive_operations 94 | - null_check_on_nullable_type_parameter 95 | - null_closures 96 | - only_throw_errors 97 | - overridden_fields 98 | - package_api_docs 99 | - package_names 100 | - package_prefixed_library_names 101 | - parameter_assignments 102 | - prefer_adjacent_string_concatenation 103 | - prefer_asserts_in_initializer_lists 104 | - prefer_asserts_with_message 105 | - prefer_collection_literals 106 | - prefer_conditional_assignment 107 | - prefer_const_constructors 108 | - prefer_const_constructors_in_immutables 109 | - prefer_const_declarations 110 | - prefer_const_literals_to_create_immutables 111 | - prefer_constructors_over_static_methods 112 | - prefer_contains 113 | - prefer_final_fields 114 | - prefer_final_in_for_each 115 | - prefer_final_locals 116 | - prefer_for_elements_to_map_fromIterable 117 | - prefer_function_declarations_over_variables 118 | - prefer_generic_function_type_aliases 119 | - prefer_if_elements_to_conditional_expressions 120 | - prefer_if_null_operators 121 | - prefer_initializing_formals 122 | - prefer_inlined_adds 123 | - prefer_int_literals 124 | - prefer_interpolation_to_compose_strings 125 | - prefer_is_empty 126 | - prefer_is_not_empty 127 | - prefer_is_not_operator 128 | - prefer_iterable_whereType 129 | - prefer_null_aware_method_calls 130 | - prefer_null_aware_operators 131 | - prefer_single_quotes 132 | - prefer_spread_collections 133 | - prefer_typing_uninitialized_variables 134 | - prefer_void_to_null 135 | - provide_deprecation_message 136 | - public_member_api_docs 137 | - recursive_getters 138 | - require_trailing_commas 139 | - secure_pubspec_urls 140 | - sized_box_for_whitespace 141 | - sized_box_shrink_expand 142 | - slash_for_doc_comments 143 | - sort_child_properties_last 144 | - sort_constructors_first 145 | - sort_unnamed_constructors_first 146 | - test_types_in_equals 147 | - throw_in_finally 148 | - tighten_type_of_initializing_formals 149 | - type_annotate_public_apis 150 | - type_init_formals 151 | - unawaited_futures 152 | - unnecessary_await_in_return 153 | - unnecessary_brace_in_string_interps 154 | - unnecessary_const 155 | - unnecessary_constructor_name 156 | - unnecessary_getters_setters 157 | - unnecessary_lambdas 158 | - unnecessary_late 159 | - unnecessary_library_directive 160 | - unnecessary_new 161 | - unnecessary_null_aware_assignments 162 | - unnecessary_null_checks 163 | - unnecessary_null_in_if_null_operators 164 | - unnecessary_nullable_for_final_variable_declarations 165 | - unnecessary_overrides 166 | - unnecessary_parenthesis 167 | - unnecessary_raw_strings 168 | - unnecessary_statements 169 | - unnecessary_string_escapes 170 | - unnecessary_string_interpolations 171 | - unnecessary_this 172 | - unnecessary_to_list_in_spreads 173 | - unrelated_type_equality_checks 174 | - use_build_context_synchronously 175 | - use_colored_box 176 | - use_decorated_box 177 | - use_enums 178 | - use_full_hex_values_for_flutter_colors 179 | - use_function_type_syntax_for_parameters 180 | - use_if_null_to_convert_nulls_to_bools 181 | - use_is_even_rather_than_modulo 182 | - use_key_in_widget_constructors 183 | - use_late_for_private_fields_and_variables 184 | - use_named_constants 185 | - use_raw_strings 186 | - use_rethrow_when_possible 187 | - use_setters_to_change_properties 188 | - use_string_buffers 189 | - use_string_in_part_of_directives 190 | - use_super_parameters 191 | - use_test_throws_matchers 192 | - use_to_and_as_if_applicable 193 | - valid_regexps 194 | - void_checks 195 | -------------------------------------------------------------------------------- /packages/rest_client/lib/rest_client.dart: -------------------------------------------------------------------------------- 1 | export 'src/clients/clients.dart'; 2 | export 'src/models/models.dart'; 3 | -------------------------------------------------------------------------------- /packages/rest_client/lib/src/clients/clients.dart: -------------------------------------------------------------------------------- 1 | export 'dog_api/dog_api.dart'; 2 | -------------------------------------------------------------------------------- /packages/rest_client/lib/src/clients/dog_api/dog_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:rest_client/src/models/dog_image/dog_image.dart'; 3 | import 'package:retrofit/retrofit.dart'; 4 | 5 | part 'dog_api.g.dart'; 6 | 7 | /// 8 | @RestApi() 9 | abstract class DogApiClient { 10 | /// Constructor 11 | factory DogApiClient(Dio dio, {String baseUrl}) = _DogApiClient; 12 | 13 | /// Get random dog image 14 | @GET('/breeds/image/random') 15 | Future getDogImageRandom(); 16 | } 17 | -------------------------------------------------------------------------------- /packages/rest_client/lib/src/clients/dog_api/dog_api.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'dog_api.dart'; 4 | 5 | // ************************************************************************** 6 | // RetrofitGenerator 7 | // ************************************************************************** 8 | 9 | // ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers 10 | 11 | class _DogApiClient implements DogApiClient { 12 | _DogApiClient( 13 | this._dio, { 14 | this.baseUrl, 15 | }); 16 | 17 | final Dio _dio; 18 | 19 | String? baseUrl; 20 | 21 | @override 22 | Future getDogImageRandom() async { 23 | const _extra = {}; 24 | final queryParameters = {}; 25 | final _headers = {}; 26 | final Map? _data = null; 27 | final _result = 28 | await _dio.fetch>(_setStreamType(Options( 29 | method: 'GET', 30 | headers: _headers, 31 | extra: _extra, 32 | ) 33 | .compose( 34 | _dio.options, 35 | '/breeds/image/random', 36 | queryParameters: queryParameters, 37 | data: _data, 38 | ) 39 | .copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl))); 40 | final value = DogImage.fromJson(_result.data!); 41 | return value; 42 | } 43 | 44 | RequestOptions _setStreamType(RequestOptions requestOptions) { 45 | if (T != dynamic && 46 | !(requestOptions.responseType == ResponseType.bytes || 47 | requestOptions.responseType == ResponseType.stream)) { 48 | if (T == String) { 49 | requestOptions.responseType = ResponseType.plain; 50 | } else { 51 | requestOptions.responseType = ResponseType.json; 52 | } 53 | } 54 | return requestOptions; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/rest_client/lib/src/converters/color_converter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | 4 | /// Convert json to color and vice versa 5 | class ColorConverter implements JsonConverter { 6 | /// 7 | const ColorConverter(); 8 | 9 | @override 10 | Color fromJson(String json) => Color(int.parse(json)); 11 | 12 | @override 13 | String toJson(Color color) => color.value.toRadixString(16); 14 | } 15 | 16 | /// How to use JsonConverter for special property 17 | // @Freezed(fromJson: true) 18 | // @ColorConverter() 19 | // class DogImage with _$DogImage { 20 | // const factory DogImage({ 21 | // required String message, 22 | // required String status, 23 | // @ColorConverter() 24 | // required Color color, 25 | // }) = _DogImage; 26 | // 27 | // factory DogImage.fromJson(Map json) => 28 | // _$DogImageFromJson(json); 29 | // } 30 | -------------------------------------------------------------------------------- /packages/rest_client/lib/src/models/dog_image/dog_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'dog_image.freezed.dart'; 4 | part 'dog_image.g.dart'; 5 | 6 | /// Image dog data 7 | @Freezed(fromJson: true) 8 | class DogImage with _$DogImage { 9 | /// 10 | const factory DogImage({ 11 | required String message, 12 | required String status, 13 | }) = _DogImage; 14 | 15 | /// 16 | factory DogImage.fromJson(Map json) => 17 | _$DogImageFromJson(json); 18 | } 19 | -------------------------------------------------------------------------------- /packages/rest_client/lib/src/models/dog_image/dog_image.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: type=lint 4 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark 5 | 6 | part of 'dog_image.dart'; 7 | 8 | // ************************************************************************** 9 | // FreezedGenerator 10 | // ************************************************************************** 11 | 12 | T _$identity(T value) => value; 13 | 14 | final _privateConstructorUsedError = UnsupportedError( 15 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 16 | 17 | DogImage _$DogImageFromJson(Map json) { 18 | return _DogImage.fromJson(json); 19 | } 20 | 21 | /// @nodoc 22 | mixin _$DogImage { 23 | String get message => throw _privateConstructorUsedError; 24 | String get status => throw _privateConstructorUsedError; 25 | 26 | Map toJson() => throw _privateConstructorUsedError; 27 | @JsonKey(ignore: true) 28 | $DogImageCopyWith get copyWith => 29 | throw _privateConstructorUsedError; 30 | } 31 | 32 | /// @nodoc 33 | abstract class $DogImageCopyWith<$Res> { 34 | factory $DogImageCopyWith(DogImage value, $Res Function(DogImage) then) = 35 | _$DogImageCopyWithImpl<$Res, DogImage>; 36 | @useResult 37 | $Res call({String message, String status}); 38 | } 39 | 40 | /// @nodoc 41 | class _$DogImageCopyWithImpl<$Res, $Val extends DogImage> 42 | implements $DogImageCopyWith<$Res> { 43 | _$DogImageCopyWithImpl(this._value, this._then); 44 | 45 | // ignore: unused_field 46 | final $Val _value; 47 | // ignore: unused_field 48 | final $Res Function($Val) _then; 49 | 50 | @pragma('vm:prefer-inline') 51 | @override 52 | $Res call({ 53 | Object? message = null, 54 | Object? status = null, 55 | }) { 56 | return _then(_value.copyWith( 57 | message: null == message 58 | ? _value.message 59 | : message // ignore: cast_nullable_to_non_nullable 60 | as String, 61 | status: null == status 62 | ? _value.status 63 | : status // ignore: cast_nullable_to_non_nullable 64 | as String, 65 | ) as $Val); 66 | } 67 | } 68 | 69 | /// @nodoc 70 | abstract class _$$_DogImageCopyWith<$Res> implements $DogImageCopyWith<$Res> { 71 | factory _$$_DogImageCopyWith( 72 | _$_DogImage value, $Res Function(_$_DogImage) then) = 73 | __$$_DogImageCopyWithImpl<$Res>; 74 | @override 75 | @useResult 76 | $Res call({String message, String status}); 77 | } 78 | 79 | /// @nodoc 80 | class __$$_DogImageCopyWithImpl<$Res> 81 | extends _$DogImageCopyWithImpl<$Res, _$_DogImage> 82 | implements _$$_DogImageCopyWith<$Res> { 83 | __$$_DogImageCopyWithImpl( 84 | _$_DogImage _value, $Res Function(_$_DogImage) _then) 85 | : super(_value, _then); 86 | 87 | @pragma('vm:prefer-inline') 88 | @override 89 | $Res call({ 90 | Object? message = null, 91 | Object? status = null, 92 | }) { 93 | return _then(_$_DogImage( 94 | message: null == message 95 | ? _value.message 96 | : message // ignore: cast_nullable_to_non_nullable 97 | as String, 98 | status: null == status 99 | ? _value.status 100 | : status // ignore: cast_nullable_to_non_nullable 101 | as String, 102 | )); 103 | } 104 | } 105 | 106 | /// @nodoc 107 | @JsonSerializable() 108 | class _$_DogImage implements _DogImage { 109 | const _$_DogImage({required this.message, required this.status}); 110 | 111 | factory _$_DogImage.fromJson(Map json) => 112 | _$$_DogImageFromJson(json); 113 | 114 | @override 115 | final String message; 116 | @override 117 | final String status; 118 | 119 | @override 120 | String toString() { 121 | return 'DogImage(message: $message, status: $status)'; 122 | } 123 | 124 | @override 125 | bool operator ==(dynamic other) { 126 | return identical(this, other) || 127 | (other.runtimeType == runtimeType && 128 | other is _$_DogImage && 129 | (identical(other.message, message) || other.message == message) && 130 | (identical(other.status, status) || other.status == status)); 131 | } 132 | 133 | @JsonKey(ignore: true) 134 | @override 135 | int get hashCode => Object.hash(runtimeType, message, status); 136 | 137 | @JsonKey(ignore: true) 138 | @override 139 | @pragma('vm:prefer-inline') 140 | _$$_DogImageCopyWith<_$_DogImage> get copyWith => 141 | __$$_DogImageCopyWithImpl<_$_DogImage>(this, _$identity); 142 | 143 | @override 144 | Map toJson() { 145 | return _$$_DogImageToJson( 146 | this, 147 | ); 148 | } 149 | } 150 | 151 | abstract class _DogImage implements DogImage { 152 | const factory _DogImage( 153 | {required final String message, 154 | required final String status}) = _$_DogImage; 155 | 156 | factory _DogImage.fromJson(Map json) = _$_DogImage.fromJson; 157 | 158 | @override 159 | String get message; 160 | @override 161 | String get status; 162 | @override 163 | @JsonKey(ignore: true) 164 | _$$_DogImageCopyWith<_$_DogImage> get copyWith => 165 | throw _privateConstructorUsedError; 166 | } 167 | -------------------------------------------------------------------------------- /packages/rest_client/lib/src/models/dog_image/dog_image.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'dog_image.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_DogImage _$$_DogImageFromJson(Map json) => _$_DogImage( 10 | message: json['message'] as String, 11 | status: json['status'] as String, 12 | ); 13 | 14 | Map _$$_DogImageToJson(_$_DogImage instance) => 15 | { 16 | 'message': instance.message, 17 | 'status': instance.status, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/rest_client/lib/src/models/models.dart: -------------------------------------------------------------------------------- 1 | export 'dog_image/dog_image.dart'; 2 | -------------------------------------------------------------------------------- /packages/rest_client/lib/src/paging/paging.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: invalid_annotation_target 2 | 3 | import 'package:freezed_annotation/freezed_annotation.dart'; 4 | part 'paging.freezed.dart'; 5 | part 'paging.g.dart'; 6 | 7 | /// Model base to get data from api pagination response 8 | /// Loadmore using this, example Paging 9 | @Freezed( 10 | genericArgumentFactories: true, 11 | ) 12 | class Paging with _$Paging { 13 | /// Constructor 14 | const factory Paging({ 15 | required List items, 16 | @JsonKey(name: 'totalCount') int? totalCount, 17 | @JsonKey(name: 'currentCount') int? currentCount, 18 | }) = _Paging; 19 | 20 | /// 21 | factory Paging.fromJson( 22 | Map json, 23 | T Function(Object?) fromJsonT, 24 | ) => 25 | _$PagingFromJson(json, fromJsonT); 26 | } 27 | -------------------------------------------------------------------------------- /packages/rest_client/lib/src/paging/paging.freezed.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | // GENERATED CODE - DO NOT MODIFY BY HAND 3 | // ignore_for_file: type=lint 4 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark 5 | 6 | part of 'paging.dart'; 7 | 8 | // ************************************************************************** 9 | // FreezedGenerator 10 | // ************************************************************************** 11 | 12 | T _$identity(T value) => value; 13 | 14 | final _privateConstructorUsedError = UnsupportedError( 15 | 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); 16 | 17 | Paging _$PagingFromJson( 18 | Map json, T Function(Object?) fromJsonT) { 19 | return _Paging.fromJson(json, fromJsonT); 20 | } 21 | 22 | /// @nodoc 23 | mixin _$Paging { 24 | List get items => throw _privateConstructorUsedError; 25 | @JsonKey(name: 'totalCount') 26 | int? get totalCount => throw _privateConstructorUsedError; 27 | @JsonKey(name: 'currentCount') 28 | int? get currentCount => throw _privateConstructorUsedError; 29 | 30 | Map toJson(Object? Function(T) toJsonT) => 31 | throw _privateConstructorUsedError; 32 | @JsonKey(ignore: true) 33 | $PagingCopyWith> get copyWith => 34 | throw _privateConstructorUsedError; 35 | } 36 | 37 | /// @nodoc 38 | abstract class $PagingCopyWith { 39 | factory $PagingCopyWith(Paging value, $Res Function(Paging) then) = 40 | _$PagingCopyWithImpl>; 41 | @useResult 42 | $Res call( 43 | {List items, 44 | @JsonKey(name: 'totalCount') int? totalCount, 45 | @JsonKey(name: 'currentCount') int? currentCount}); 46 | } 47 | 48 | /// @nodoc 49 | class _$PagingCopyWithImpl> 50 | implements $PagingCopyWith { 51 | _$PagingCopyWithImpl(this._value, this._then); 52 | 53 | // ignore: unused_field 54 | final $Val _value; 55 | // ignore: unused_field 56 | final $Res Function($Val) _then; 57 | 58 | @pragma('vm:prefer-inline') 59 | @override 60 | $Res call({ 61 | Object? items = null, 62 | Object? totalCount = freezed, 63 | Object? currentCount = freezed, 64 | }) { 65 | return _then(_value.copyWith( 66 | items: null == items 67 | ? _value.items 68 | : items // ignore: cast_nullable_to_non_nullable 69 | as List, 70 | totalCount: freezed == totalCount 71 | ? _value.totalCount 72 | : totalCount // ignore: cast_nullable_to_non_nullable 73 | as int?, 74 | currentCount: freezed == currentCount 75 | ? _value.currentCount 76 | : currentCount // ignore: cast_nullable_to_non_nullable 77 | as int?, 78 | ) as $Val); 79 | } 80 | } 81 | 82 | /// @nodoc 83 | abstract class _$$_PagingCopyWith implements $PagingCopyWith { 84 | factory _$$_PagingCopyWith( 85 | _$_Paging value, $Res Function(_$_Paging) then) = 86 | __$$_PagingCopyWithImpl; 87 | @override 88 | @useResult 89 | $Res call( 90 | {List items, 91 | @JsonKey(name: 'totalCount') int? totalCount, 92 | @JsonKey(name: 'currentCount') int? currentCount}); 93 | } 94 | 95 | /// @nodoc 96 | class __$$_PagingCopyWithImpl 97 | extends _$PagingCopyWithImpl> 98 | implements _$$_PagingCopyWith { 99 | __$$_PagingCopyWithImpl( 100 | _$_Paging _value, $Res Function(_$_Paging) _then) 101 | : super(_value, _then); 102 | 103 | @pragma('vm:prefer-inline') 104 | @override 105 | $Res call({ 106 | Object? items = null, 107 | Object? totalCount = freezed, 108 | Object? currentCount = freezed, 109 | }) { 110 | return _then(_$_Paging( 111 | items: null == items 112 | ? _value._items 113 | : items // ignore: cast_nullable_to_non_nullable 114 | as List, 115 | totalCount: freezed == totalCount 116 | ? _value.totalCount 117 | : totalCount // ignore: cast_nullable_to_non_nullable 118 | as int?, 119 | currentCount: freezed == currentCount 120 | ? _value.currentCount 121 | : currentCount // ignore: cast_nullable_to_non_nullable 122 | as int?, 123 | )); 124 | } 125 | } 126 | 127 | /// @nodoc 128 | @JsonSerializable(genericArgumentFactories: true) 129 | class _$_Paging implements _Paging { 130 | const _$_Paging( 131 | {required final List items, 132 | @JsonKey(name: 'totalCount') this.totalCount, 133 | @JsonKey(name: 'currentCount') this.currentCount}) 134 | : _items = items; 135 | 136 | factory _$_Paging.fromJson( 137 | Map json, T Function(Object?) fromJsonT) => 138 | _$$_PagingFromJson(json, fromJsonT); 139 | 140 | final List _items; 141 | @override 142 | List get items { 143 | if (_items is EqualUnmodifiableListView) return _items; 144 | // ignore: implicit_dynamic_type 145 | return EqualUnmodifiableListView(_items); 146 | } 147 | 148 | @override 149 | @JsonKey(name: 'totalCount') 150 | final int? totalCount; 151 | @override 152 | @JsonKey(name: 'currentCount') 153 | final int? currentCount; 154 | 155 | @override 156 | String toString() { 157 | return 'Paging<$T>(items: $items, totalCount: $totalCount, currentCount: $currentCount)'; 158 | } 159 | 160 | @override 161 | bool operator ==(dynamic other) { 162 | return identical(this, other) || 163 | (other.runtimeType == runtimeType && 164 | other is _$_Paging && 165 | const DeepCollectionEquality().equals(other._items, _items) && 166 | (identical(other.totalCount, totalCount) || 167 | other.totalCount == totalCount) && 168 | (identical(other.currentCount, currentCount) || 169 | other.currentCount == currentCount)); 170 | } 171 | 172 | @JsonKey(ignore: true) 173 | @override 174 | int get hashCode => Object.hash(runtimeType, 175 | const DeepCollectionEquality().hash(_items), totalCount, currentCount); 176 | 177 | @JsonKey(ignore: true) 178 | @override 179 | @pragma('vm:prefer-inline') 180 | _$$_PagingCopyWith> get copyWith => 181 | __$$_PagingCopyWithImpl>(this, _$identity); 182 | 183 | @override 184 | Map toJson(Object? Function(T) toJsonT) { 185 | return _$$_PagingToJson(this, toJsonT); 186 | } 187 | } 188 | 189 | abstract class _Paging implements Paging { 190 | const factory _Paging( 191 | {required final List items, 192 | @JsonKey(name: 'totalCount') final int? totalCount, 193 | @JsonKey(name: 'currentCount') final int? currentCount}) = _$_Paging; 194 | 195 | factory _Paging.fromJson( 196 | Map json, T Function(Object?) fromJsonT) = 197 | _$_Paging.fromJson; 198 | 199 | @override 200 | List get items; 201 | @override 202 | @JsonKey(name: 'totalCount') 203 | int? get totalCount; 204 | @override 205 | @JsonKey(name: 'currentCount') 206 | int? get currentCount; 207 | @override 208 | @JsonKey(ignore: true) 209 | _$$_PagingCopyWith> get copyWith => 210 | throw _privateConstructorUsedError; 211 | } 212 | -------------------------------------------------------------------------------- /packages/rest_client/lib/src/paging/paging.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'paging.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Paging _$$_PagingFromJson( 10 | Map json, 11 | T Function(Object? json) fromJsonT, 12 | ) => 13 | _$_Paging( 14 | items: (json['items'] as List).map(fromJsonT).toList(), 15 | totalCount: json['totalCount'] as int?, 16 | currentCount: json['currentCount'] as int?, 17 | ); 18 | 19 | Map _$$_PagingToJson( 20 | _$_Paging instance, 21 | Object? Function(T value) toJsonT, 22 | ) => 23 | { 24 | 'items': instance.items.map(toJsonT).toList(), 25 | 'totalCount': instance.totalCount, 26 | 'currentCount': instance.currentCount, 27 | }; 28 | -------------------------------------------------------------------------------- /packages/rest_client/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: rest_client 2 | description: A new Flutter project. 3 | version: 0.0.1 4 | homepage: 5 | 6 | environment: 7 | sdk: '>=3.6.0 <4.0.0' 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dio: ^5.8.0+1 14 | json_annotation: ^4.8.0 15 | freezed_annotation: ^3.0.0 16 | retrofit: ^4.0.1 17 | logger: ^2.5.0 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | flutter_lints: ^5.0.0 23 | freezed: ^3.0.4 24 | json_serializable: ^6.6.1 25 | retrofit_generator: ^6.0.0+1 26 | build_runner: ^2.4.15 27 | 28 | # For information on the generic Dart part of this file, see the 29 | # following page: https://dart.dev/tools/pub/pubspec 30 | 31 | # The following section is specific to Flutter packages. 32 | flutter: 33 | 34 | # To add assets to your package, add an assets section, like this: 35 | # assets: 36 | # - images/a_dot_burr.jpeg 37 | # - images/a_dot_ham.jpeg 38 | # 39 | # For details regarding assets in packages, see 40 | # https://flutter.dev/assets-and-images/#from-packages 41 | # 42 | # An image asset can refer to one or more resolution-specific "variants", see 43 | # https://flutter.dev/assets-and-images/#resolution-aware 44 | 45 | # To add custom fonts to your package, add a fonts section here, 46 | # in this "flutter" section. Each entry in this list should have a 47 | # "family" key with the font family name, and a "fonts" key with a 48 | # list giving the asset and other descriptors for the font. For 49 | # example: 50 | # fonts: 51 | # - family: Schyler 52 | # fonts: 53 | # - asset: fonts/Schyler-Regular.ttf 54 | # - asset: fonts/Schyler-Italic.ttf 55 | # style: italic 56 | # - family: Trajan Pro 57 | # fonts: 58 | # - asset: fonts/TrajanPro.ttf 59 | # - asset: fonts/TrajanPro_Bold.ttf 60 | # weight: 700 61 | # 62 | # For details regarding fonts in packages, see 63 | # https://flutter.dev/custom-fonts/#from-packages 64 | -------------------------------------------------------------------------------- /packages/rest_client/test/rest_client_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:rest_client/rest_client.dart'; 4 | 5 | void main() { 6 | final Dio dio = Dio( 7 | BaseOptions( 8 | baseUrl: 'https://dog.ceo/api', 9 | ), 10 | ); 11 | 12 | final DogApiClient dogApiClient = DogApiClient(dio); 13 | 14 | test('test', () async { 15 | final dogImage = await dogApiClient.getDogImageRandom(); 16 | 17 | expect(dogImage.message, isNotEmpty); 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: boilerplate 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | # In Windows, build-name is used as the major, minor, and patch parts 19 | # of the product and file versions while build-number is used as the build suffix. 20 | version: 1.0.0+1 21 | 22 | environment: 23 | sdk: '>=3.6.0 <4.0.0' 24 | 25 | # Dependencies specify other packages that your package needs in order to work. 26 | # To automatically upgrade your package dependencies to the latest versions 27 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 28 | # dependencies can be manually updated by changing the version numbers below to 29 | # the latest version available on pub.dev. To see which dependencies have newer 30 | # versions available, run `flutter pub outdated`. 31 | dependencies: 32 | flutter: 33 | sdk: flutter 34 | flutter_localizations: 35 | sdk: flutter 36 | flutter_bloc: ^9.1.0 37 | bloc_concurrency: ^0.3.0 38 | get_it: ^8.0.3 39 | injectable: ^2.5.0 40 | freezed_annotation: ^3.0.0 41 | dio: ^5.8.0+1 42 | intl: ^0.19.0 43 | intl_utils: ^2.8.10 44 | rxdart: ^0.28.0 45 | logger: ^2.5.0 46 | flutter_svg: ^2.0.17 47 | go_router: ^14.8.1 48 | cupertino_icons: ^1.0.8 49 | another_flushbar: ^1.12.29 50 | shared_preferences: ^2.5.3 51 | pretty_dio_logger: ^1.4.0 52 | 53 | rest_client: 54 | path: packages/rest_client 55 | 56 | local_database: 57 | path: packages/local_database 58 | 59 | dev_dependencies: 60 | flutter_test: 61 | sdk: flutter 62 | integration_test: 63 | sdk: flutter 64 | 65 | # The "flutter_lints" package below contains a set of recommended lints to 66 | # encourage good coding practices. The lint set provided by the package is 67 | # activated in the `analysis_options.yaml` file located at the root of your 68 | # package. See that file for information about deactivating specific lint 69 | # rules and activating additional ones. 70 | flutter_lints: ^5.0.0 71 | freezed: ^3.0.4 72 | build_runner: ^2.4.15 73 | bloc_test: ^10.0.0 74 | mockito: ^5.4.5 75 | flutter_gen_runner: 76 | 77 | # For information on the generic Dart part of this file, see the 78 | # following page: https://dart.dev/tools/pub/pubspec 79 | 80 | # The following section is specific to Flutter packages. 81 | flutter_gen: 82 | output: lib/generated/ 83 | integrations: 84 | flutter_svg: true 85 | 86 | flutter: 87 | 88 | # The following line ensures that the Material Icons font is 89 | # included with your application, so that you can use the icons in 90 | # the material Icons class. 91 | uses-material-design: true 92 | 93 | assets: 94 | - assets/images/ 95 | - assets/icons/ 96 | 97 | # To add assets to your application, add an assets section, like this: 98 | # assets: 99 | # - images/a_dot_burr.jpeg 100 | # - images/a_dot_ham.jpeg 101 | 102 | # An image asset can refer to one or more resolution-specific "variants", see 103 | # https://flutter.dev/assets-and-images/#resolution-aware 104 | 105 | # For details regarding adding assets from package dependencies, see 106 | # https://flutter.dev/assets-and-images/#from-packages 107 | 108 | # To add custom fonts to your application, add a fonts section here, 109 | # in this "flutter" section. Each entry in this list should have a 110 | # "family" key with the font family name, and a "fonts" key with a 111 | # list giving the asset and other descriptors for the font. For 112 | # example: 113 | # fonts: 114 | # - family: Schyler 115 | # fonts: 116 | # - asset: fonts/Schyler-Regular.ttf 117 | # - asset: fonts/Schyler-Italic.ttf 118 | # style: italic 119 | # - family: Trajan Pro 120 | # fonts: 121 | # - asset: fonts/TrajanPro.ttf 122 | # - asset: fonts/TrajanPro_Bold.ttf 123 | # weight: 700 124 | # 125 | # For details regarding fonts from package dependencies, 126 | # see https://flutter.dev/custom-fonts/#from-packages 127 | flutter_intl: 128 | enabled: true 129 | -------------------------------------------------------------------------------- /readme_attach/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/readme_attach/architecture.png -------------------------------------------------------------------------------- /test/dependencies/mock_dependencies.dart: -------------------------------------------------------------------------------- 1 | import 'package:boilerplate/data/repositories/dog_image_random/local/dog_image_local_repository.dart'; 2 | import 'package:boilerplate/data/repositories/dog_image_random/remote/dog_image_random_repository.dart'; 3 | import 'package:boilerplate/services/app_service/app_service.dart'; 4 | import 'package:boilerplate/services/local_storage_service/local_storage_service.dart'; 5 | import 'package:boilerplate/services/log_service/log_service.dart'; 6 | import 'package:mockito/annotations.dart'; 7 | 8 | @GenerateMocks([ 9 | DogImageRandomRepository, 10 | DogImageLocalRepository, 11 | LogService, 12 | AppService, 13 | LocalStorageService, 14 | 15 | // TODO(boilerplate): add more dependencies 16 | ]) 17 | void main() {} 18 | -------------------------------------------------------------------------------- /test/dependencies/mock_dependencies.mocks.dart: -------------------------------------------------------------------------------- 1 | // Mocks generated by Mockito 5.4.1 from annotations 2 | // in boilerplate/test/dependencies/mock_dependencies.dart. 3 | // Do not manually edit this file. 4 | 5 | // @dart=2.19 6 | 7 | // ignore_for_file: no_leading_underscores_for_library_prefixes 8 | import 'dart:async' as _i4; 9 | 10 | import 'package:boilerplate/data/repositories/dog_image_random/local/dog_image_local_repository.dart' 11 | as _i5; 12 | import 'package:boilerplate/data/repositories/dog_image_random/remote/dog_image_random_repository.dart' 13 | as _i3; 14 | import 'package:boilerplate/services/app_service/app_service.dart' as _i8; 15 | import 'package:boilerplate/services/local_storage_service/local_storage_service.dart' 16 | as _i9; 17 | import 'package:boilerplate/services/log_service/log_service.dart' as _i7; 18 | import 'package:local_database/local_database.dart' as _i6; 19 | import 'package:mockito/mockito.dart' as _i1; 20 | import 'package:rest_client/rest_client.dart' as _i2; 21 | 22 | // ignore_for_file: type=lint 23 | // ignore_for_file: avoid_redundant_argument_values 24 | // ignore_for_file: avoid_setters_without_getters 25 | // ignore_for_file: comment_references 26 | // ignore_for_file: implementation_imports 27 | // ignore_for_file: invalid_use_of_visible_for_testing_member 28 | // ignore_for_file: prefer_const_constructors 29 | // ignore_for_file: unnecessary_parenthesis 30 | // ignore_for_file: camel_case_types 31 | // ignore_for_file: subtype_of_sealed_class 32 | 33 | class _FakeDogImage_0 extends _i1.SmartFake implements _i2.DogImage { 34 | _FakeDogImage_0( 35 | Object parent, 36 | Invocation parentInvocation, 37 | ) : super( 38 | parent, 39 | parentInvocation, 40 | ); 41 | } 42 | 43 | /// A class which mocks [DogImageRandomRepository]. 44 | /// 45 | /// See the documentation for Mockito's code generation for more information. 46 | class MockDogImageRandomRepository extends _i1.Mock 47 | implements _i3.DogImageRandomRepository { 48 | MockDogImageRandomRepository() { 49 | _i1.throwOnMissingStub(this); 50 | } 51 | 52 | @override 53 | _i4.Future<_i2.DogImage> getDogImageRandom() => (super.noSuchMethod( 54 | Invocation.method( 55 | #getDogImageRandom, 56 | [], 57 | ), 58 | returnValue: _i4.Future<_i2.DogImage>.value(_FakeDogImage_0( 59 | this, 60 | Invocation.method( 61 | #getDogImageRandom, 62 | [], 63 | ), 64 | )), 65 | ) as _i4.Future<_i2.DogImage>); 66 | } 67 | 68 | /// A class which mocks [DogImageLocalRepository]. 69 | /// 70 | /// See the documentation for Mockito's code generation for more information. 71 | class MockDogImageLocalRepository extends _i1.Mock 72 | implements _i5.DogImageLocalRepository { 73 | MockDogImageLocalRepository() { 74 | _i1.throwOnMissingStub(this); 75 | } 76 | 77 | @override 78 | _i4.Future insertDogImageDB(_i6.DogImageEntity? dogImageEntity) => 79 | (super.noSuchMethod( 80 | Invocation.method( 81 | #insertDogImageDB, 82 | [dogImageEntity], 83 | ), 84 | returnValue: _i4.Future.value(), 85 | returnValueForMissingStub: _i4.Future.value(), 86 | ) as _i4.Future); 87 | @override 88 | _i4.Future> getDogImagesFromDB() => 89 | (super.noSuchMethod( 90 | Invocation.method( 91 | #getDogImagesFromDB, 92 | [], 93 | ), 94 | returnValue: 95 | _i4.Future>.value(<_i6.DogImageEntity>[]), 96 | ) as _i4.Future>); 97 | @override 98 | _i4.Future deleteDogImageDB(_i6.DogImageEntity? dogImageEntity) => 99 | (super.noSuchMethod( 100 | Invocation.method( 101 | #deleteDogImageDB, 102 | [dogImageEntity], 103 | ), 104 | returnValue: _i4.Future.value(), 105 | returnValueForMissingStub: _i4.Future.value(), 106 | ) as _i4.Future); 107 | } 108 | 109 | /// A class which mocks [LogService]. 110 | /// 111 | /// See the documentation for Mockito's code generation for more information. 112 | class MockLogService extends _i1.Mock implements _i7.LogService { 113 | MockLogService() { 114 | _i1.throwOnMissingStub(this); 115 | } 116 | 117 | @override 118 | void e( 119 | String? message, 120 | dynamic e, 121 | StackTrace? stack, 122 | ) => 123 | super.noSuchMethod( 124 | Invocation.method( 125 | #e, 126 | [ 127 | message, 128 | e, 129 | stack, 130 | ], 131 | ), 132 | returnValueForMissingStub: null, 133 | ); 134 | @override 135 | void i(String? message) => super.noSuchMethod( 136 | Invocation.method( 137 | #i, 138 | [message], 139 | ), 140 | returnValueForMissingStub: null, 141 | ); 142 | @override 143 | void w( 144 | String? message, [ 145 | dynamic e, 146 | StackTrace? stack, 147 | ]) => 148 | super.noSuchMethod( 149 | Invocation.method( 150 | #w, 151 | [ 152 | message, 153 | e, 154 | stack, 155 | ], 156 | ), 157 | returnValueForMissingStub: null, 158 | ); 159 | } 160 | 161 | /// A class which mocks [AppService]. 162 | /// 163 | /// See the documentation for Mockito's code generation for more information. 164 | class MockAppService extends _i1.Mock implements _i8.AppService { 165 | MockAppService() { 166 | _i1.throwOnMissingStub(this); 167 | } 168 | 169 | @override 170 | String get locale => (super.noSuchMethod( 171 | Invocation.getter(#locale), 172 | returnValue: '', 173 | ) as String); 174 | @override 175 | bool get isDarkMode => (super.noSuchMethod( 176 | Invocation.getter(#isDarkMode), 177 | returnValue: false, 178 | ) as bool); 179 | @override 180 | bool get isFirstUse => (super.noSuchMethod( 181 | Invocation.getter(#isFirstUse), 182 | returnValue: false, 183 | ) as bool); 184 | @override 185 | _i4.Future setLocale({required String? locale}) => (super.noSuchMethod( 186 | Invocation.method( 187 | #setLocale, 188 | [], 189 | {#locale: locale}, 190 | ), 191 | returnValue: _i4.Future.value(), 192 | returnValueForMissingStub: _i4.Future.value(), 193 | ) as _i4.Future); 194 | @override 195 | _i4.Future setIsDarkMode({required bool? darkMode}) => 196 | (super.noSuchMethod( 197 | Invocation.method( 198 | #setIsDarkMode, 199 | [], 200 | {#darkMode: darkMode}, 201 | ), 202 | returnValue: _i4.Future.value(), 203 | returnValueForMissingStub: _i4.Future.value(), 204 | ) as _i4.Future); 205 | @override 206 | _i4.Future setIsFirstUse({required bool? isFirstUse}) => 207 | (super.noSuchMethod( 208 | Invocation.method( 209 | #setIsFirstUse, 210 | [], 211 | {#isFirstUse: isFirstUse}, 212 | ), 213 | returnValue: _i4.Future.value(), 214 | returnValueForMissingStub: _i4.Future.value(), 215 | ) as _i4.Future); 216 | } 217 | 218 | /// A class which mocks [LocalStorageService]. 219 | /// 220 | /// See the documentation for Mockito's code generation for more information. 221 | class MockLocalStorageService extends _i1.Mock 222 | implements _i9.LocalStorageService { 223 | MockLocalStorageService() { 224 | _i1.throwOnMissingStub(this); 225 | } 226 | 227 | @override 228 | _i4.FutureOr setValue({ 229 | required String? key, 230 | required dynamic value, 231 | }) => 232 | (super.noSuchMethod(Invocation.method( 233 | #setValue, 234 | [], 235 | { 236 | #key: key, 237 | #value: value, 238 | }, 239 | )) as _i4.FutureOr); 240 | @override 241 | Object? getValue({required String? key}) => 242 | (super.noSuchMethod(Invocation.method( 243 | #getValue, 244 | [], 245 | {#key: key}, 246 | )) as Object?); 247 | @override 248 | String? getString({required String? key}) => 249 | (super.noSuchMethod(Invocation.method( 250 | #getString, 251 | [], 252 | {#key: key}, 253 | )) as String?); 254 | @override 255 | int? getInt({required String? key}) => (super.noSuchMethod(Invocation.method( 256 | #getInt, 257 | [], 258 | {#key: key}, 259 | )) as int?); 260 | @override 261 | double? getDouble({required String? key}) => 262 | (super.noSuchMethod(Invocation.method( 263 | #getDouble, 264 | [], 265 | {#key: key}, 266 | )) as double?); 267 | @override 268 | bool? getBool({required String? key}) => 269 | (super.noSuchMethod(Invocation.method( 270 | #getBool, 271 | [], 272 | {#key: key}, 273 | )) as bool?); 274 | @override 275 | List? getStringList({required String? key}) => 276 | (super.noSuchMethod(Invocation.method( 277 | #getStringList, 278 | [], 279 | {#key: key}, 280 | )) as List?); 281 | @override 282 | _i4.FutureOr removeEntry({required String? key}) => (super.noSuchMethod( 283 | Invocation.method( 284 | #removeEntry, 285 | [], 286 | {#key: key}, 287 | ), 288 | returnValue: _i4.Future.value(false), 289 | ) as _i4.FutureOr); 290 | } 291 | -------------------------------------------------------------------------------- /test/features/dog_image_random_bloc_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc_test/bloc_test.dart'; 2 | import 'package:boilerplate/core/bloc_core/ui_status.dart'; 3 | import 'package:boilerplate/data/repositories/dog_image_random/remote/dog_image_random_repository.dart'; 4 | import 'package:boilerplate/features/dog_image_random/bloc/dog_image_random_bloc.dart'; 5 | import 'package:boilerplate/services/log_service/log_service.dart'; 6 | import 'package:flutter_test/flutter_test.dart'; 7 | import 'package:mockito/mockito.dart'; 8 | import 'package:rest_client/rest_client.dart'; 9 | import '../dependencies/mock_dependencies.mocks.dart'; 10 | 11 | void main() { 12 | const DogImage image = DogImage(message: 'demo', status: 'success'); 13 | final DogImageRandomRepository repository = MockDogImageRandomRepository(); 14 | final LogService logService = MockLogService(); 15 | 16 | late DogImageRandomBloc bloc; 17 | 18 | setUp(() { 19 | bloc = DogImageRandomBloc( 20 | dogImageRandomRepository: repository, 21 | logService: logService, 22 | ); 23 | }); 24 | 25 | group('test add event [DogImageRandomRandomRequested]', () { 26 | blocTest( 27 | 'emit state when success', 28 | setUp: () { 29 | when(repository.getDogImageRandom()).thenAnswer( 30 | (_) => Future.value( 31 | image, 32 | ), 33 | ); 34 | }, 35 | build: () => bloc, 36 | act: (_) => bloc.add( 37 | const DogImageRandomEvent.randomRequested(), 38 | ), 39 | expect: () => [ 40 | isA().having( 41 | (state) => state.isBusy, 42 | 'isBusy', 43 | true, 44 | ), 45 | isA() 46 | .having( 47 | (state) => state.isBusy, 48 | 'isBusy', 49 | false, 50 | ) 51 | .having( 52 | (state) => state.status, 53 | 'status', 54 | isA(), 55 | ) 56 | .having( 57 | (state) => state.dogImage, 58 | 'image', 59 | image, 60 | ), 61 | ], 62 | ); 63 | 64 | blocTest( 65 | 'emit state when failed', 66 | setUp: () { 67 | when(repository.getDogImageRandom()).thenThrow( 68 | Exception( 69 | 'error', 70 | ), 71 | ); 72 | }, 73 | build: () => bloc, 74 | seed: () => const DogImageRandomState(dogImage: image), 75 | act: (_) => bloc.add( 76 | const DogImageRandomEvent.randomRequested(), 77 | ), 78 | expect: () => [ 79 | isA().having( 80 | (state) => state.isBusy, 81 | 'isBusy', 82 | true, 83 | ), 84 | isA() 85 | .having( 86 | (state) => state.notification, 87 | 'status', 88 | isNotNull, 89 | ) 90 | .having( 91 | (state) => state.dogImage, 92 | 'image', 93 | image, 94 | ) 95 | .having( 96 | (state) => state.isBusy, 97 | 'isBusy', 98 | false, 99 | ), 100 | ], 101 | ); 102 | }); 103 | } 104 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | void main() {} 9 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeref278/flutter_boilerplate/921dc57cee8d49f3809cc4607ee8c19d99a226b5/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | boilerplate 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boilerplate", 3 | "short_name": "boilerplate", 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 | } 36 | --------------------------------------------------------------------------------