├── .gitignore ├── .metadata ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── vn │ │ │ │ └── newwave │ │ │ │ └── flutter_app │ │ │ │ └── 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 ├── fonts │ ├── Arimo-Bold.ttf │ ├── Arimo-BoldItalic.ttf │ ├── Arimo-Italic.ttf │ ├── Arimo-Medium.ttf │ ├── Arimo-MediumItalic.ttf │ ├── Arimo-Regular.ttf │ ├── Arimo-SemiBold.ttf │ └── Arimo-SemiBoldItalic.ttf └── images │ ├── 2.0x │ ├── bg_image_placeholder.png │ ├── ic_avatar.png │ ├── ic_back.png │ ├── ic_eye_close.png │ ├── ic_eye_open.png │ ├── ic_image_placeholder.png │ ├── ic_logo_transparent.png │ └── ic_white_back.png │ └── 3.0x │ ├── bg_image_placeholder.png │ ├── ic_avatar.png │ ├── ic_back.png │ ├── ic_eye_close.png │ ├── ic_eye_open.png │ ├── ic_image_placeholder.png │ ├── ic_logo.png │ ├── ic_logo_transparent.png │ └── ic_white_back.png ├── 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 ├── bindings │ ├── initial_binding.dart │ └── repository_bindings.dart ├── common │ ├── app_colors.dart │ ├── app_dimens.dart │ ├── app_images.dart │ ├── app_shadows.dart │ ├── app_text_styles.dart │ └── app_themes.dart ├── configs │ └── app_configs.dart ├── database │ ├── secure_storage_helper.dart │ └── share_preferences_helper.dart ├── l10n │ ├── intl_en.arb │ └── intl_vi.arb ├── main.dart ├── model │ ├── entities │ │ ├── index.dart │ │ ├── movie_entity.dart │ │ ├── notification │ │ │ └── notification_entity.dart │ │ ├── token_entity.dart │ │ └── user │ │ │ └── user_entity.dart │ ├── enums │ │ ├── gender_type.dart │ │ └── load_status.dart │ ├── params │ │ ├── index.dart │ │ └── sign_up_param.dart │ └── response │ │ ├── array_response.dart │ │ └── object_response.dart ├── networks │ ├── api_client.dart │ ├── api_interceptors.dart │ └── api_util.dart ├── repositories │ ├── auth_repository.dart │ ├── movie_repository.dart │ ├── notification_repository.dart │ └── user_repository.dart ├── router │ └── route_config.dart ├── services │ ├── app_service.dart │ └── setting_service.dart ├── ui │ ├── commons │ │ ├── app_bottom_sheet.dart │ │ ├── app_dialog.dart │ │ └── app_snackbar.dart │ ├── pages │ │ ├── forgot_password │ │ │ ├── forgot_password_cubit.dart │ │ │ ├── forgot_password_page.dart │ │ │ └── forgot_password_state.dart │ │ ├── main │ │ │ ├── main_logic.dart │ │ │ ├── main_state.dart │ │ │ ├── main_view.dart │ │ │ └── tab │ │ │ │ └── main_tab.dart │ │ ├── movie_detail │ │ │ ├── movie_detail_logic.dart │ │ │ ├── movie_detail_state.dart │ │ │ └── movie_detail_view.dart │ │ ├── setting │ │ │ └── setting_page.dart │ │ ├── sign_in │ │ │ ├── sign_in_logic.dart │ │ │ ├── sign_in_state.dart │ │ │ └── sign_in_view.dart │ │ ├── sign_up │ │ │ ├── sign_up_cubit.dart │ │ │ ├── sign_up_page.dart │ │ │ └── sign_up_state.dart │ │ ├── splash │ │ │ ├── splash_logic.dart │ │ │ ├── splash_state.dart │ │ │ └── splash_view.dart │ │ ├── tab_home │ │ │ ├── enums │ │ │ │ └── home_section.dart │ │ │ ├── home_tab_logic.dart │ │ │ ├── home_tab_state.dart │ │ │ ├── home_tab_view.dart │ │ │ ├── movies_section │ │ │ │ ├── movies_section_logic.dart │ │ │ │ ├── movies_section_state.dart │ │ │ │ ├── movies_section_view.dart │ │ │ │ └── widgets │ │ │ │ │ ├── loading_list_widget.dart │ │ │ │ │ └── movie_widget.dart │ │ │ └── widgets │ │ │ │ └── home_app_bar.dart │ │ └── tab_profile │ │ │ ├── profile_tab_logic.dart │ │ │ ├── profile_tab_state.dart │ │ │ ├── profile_tab_view.dart │ │ │ └── widgets │ │ │ ├── menu_header_widget.dart │ │ │ └── menu_item_widget.dart │ └── widgets │ │ ├── app_circular_progress_indicator.dart │ │ ├── app_devider.dart │ │ ├── appbar │ │ └── app_bar_widget.dart │ │ ├── buttons │ │ ├── app_button.dart │ │ ├── app_icon_button.dart │ │ ├── app_tint_button.dart │ │ └── app_white_button.dart │ │ ├── empty_list_widget.dart │ │ ├── error_list_widget.dart │ │ ├── images │ │ ├── app_cache_image.dart │ │ └── app_circle_avatar.dart │ │ ├── input │ │ ├── app_email_input.dart │ │ ├── app_label_text_field.dart │ │ ├── app_password_input.dart │ │ └── app_phone_input.dart │ │ ├── loading_more_row_widget.dart │ │ ├── loading_row_widget.dart │ │ ├── pickers │ │ └── app_label_date_picker.dart │ │ ├── shimmer │ │ └── app_shimmer.dart │ │ ├── tabs │ │ └── app_tab_bar.dart │ │ └── textfields │ │ └── app_text_field.dart └── utils │ ├── date_utils.dart │ ├── file_utils.dart │ ├── logger.dart │ └── utils.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | #*.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .classpath 21 | .project 22 | .settings/ 23 | .vscode/ 24 | 25 | # Flutter repo-specific 26 | /bin/cache/ 27 | /bin/mingit/ 28 | /dev/benchmarks/mega_gallery/ 29 | /dev/bots/.recipe_deps 30 | /dev/bots/android_tools/ 31 | /dev/devicelab/ABresults*.json 32 | /dev/docs/doc/ 33 | /dev/docs/flutter.docs.zip 34 | /dev/docs/lib/ 35 | /dev/docs/pubspec.yaml 36 | /dev/integration_tests/**/xcuserdata 37 | /dev/integration_tests/**/Pods 38 | /packages/flutter/coverage/ 39 | version 40 | analysis_benchmark.json 41 | 42 | # packages file containing multi-root paths 43 | .packages.generated 44 | 45 | # Flutter/Dart/Pub related 46 | **/doc/api/ 47 | **/ios/Flutter/.last_build_id 48 | .dart_tool/ 49 | .flutter-plugins 50 | .flutter-plugins-dependencies 51 | **/generated_plugin_registrant.dart 52 | .packages 53 | .pub-cache/ 54 | .pub/ 55 | build/ 56 | flutter_*.png 57 | linked_*.ds 58 | unlinked.ds 59 | unlinked_spec.ds 60 | 61 | # Android related 62 | **/android/**/gradle-wrapper.jar 63 | **/android/.gradle 64 | **/android/captures/ 65 | **/android/gradlew 66 | **/android/gradlew.bat 67 | **/android/local.properties 68 | **/android/**/GeneratedPluginRegistrant.java 69 | **/android/key.properties 70 | *.jks 71 | 72 | # iOS/XCode related 73 | **/ios/**/*.mode1v3 74 | **/ios/**/*.mode2v3 75 | **/ios/**/*.moved-aside 76 | **/ios/**/*.pbxuser 77 | **/ios/**/*.perspectivev3 78 | **/ios/**/*sync/ 79 | **/ios/**/.sconsign.dblite 80 | **/ios/**/.tags* 81 | **/ios/**/.vagrant/ 82 | **/ios/**/DerivedData/ 83 | **/ios/**/Icon? 84 | **/ios/**/Pods/ 85 | **/ios/**/.symlinks/ 86 | **/ios/**/profile 87 | **/ios/**/xcuserdata 88 | **/ios/.generated/ 89 | **/ios/Flutter/.last_build_id 90 | **/ios/Flutter/App.framework 91 | **/ios/Flutter/Flutter.framework 92 | **/ios/Flutter/Flutter.podspec 93 | **/ios/Flutter/Generated.xcconfig 94 | **/ios/Flutter/app.flx 95 | **/ios/Flutter/app.zip 96 | **/ios/Flutter/flutter_assets/ 97 | **/ios/Flutter/flutter_export_environment.sh 98 | **/ios/ServiceDefinitions.json 99 | **/ios/Runner/GeneratedPluginRegistrant.* 100 | 101 | # macOS 102 | **/macos/Flutter/GeneratedPluginRegistrant.swift 103 | **/macos/Flutter/Flutter-Debug.xcconfig 104 | **/macos/Flutter/Flutter-Release.xcconfig 105 | **/macos/Flutter/Flutter-Profile.xcconfig 106 | 107 | # Coverage 108 | coverage/ 109 | 110 | # Symbols 111 | app.*.symbols 112 | 113 | # Exceptions to above rules. 114 | !**/ios/**/default.mode1v3 115 | !**/ios/**/default.mode2v3 116 | !**/ios/**/default.pbxuser 117 | !**/ios/**/default.perspectivev3 118 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 119 | !/dev/ci/**/Gemfile.lock 120 | 121 | *.g.dart 122 | lib/generated 123 | .flutter-plugins-dependencies -------------------------------------------------------------------------------- /.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: adc687823a831bbebe28bdccfac1a628ca621513 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter-app boilerplate 2 | 3 | This repo is a boilerplate to create flutter application easily. It is based on **GetX**. More info about [GetX](https://pub.dev/packages/get) here. The app has been setup to work with [retrofit](https://pub.dev/packages/retrofit), [dio](https://pub.dev/packages/dio), [json_annotation](https://pub.dev/packages/json_annotation), [intl_utils](https://pub.dev/packages/intl_utils) and [shimmer](https://pub.dev/packages/shimmer) 4 | 5 | ## Getting Started 6 | 1. Install [Flutter SDK](https://flutter.dev/docs/get-started/install). Require Flutter 2.0 7 | 2. Install plugins in Android Studio 8 | * [Dart Data Class](https://plugins.jetbrains.com/plugin/12429-dart-data-class) 9 | * [Flutter Intl](https://plugins.jetbrains.com/plugin/13666-flutter-intl) 10 | * [GetX](https://plugins.jetbrains.com/plugin/15919-getx) 11 | 4. Clone the repo. 12 | 5. Run `flutter pub get` 13 | 6. Run `flutter pub run intl_utils:generate` 14 | 7. Run `flutter pub run build_runner build --delete-conflicting-outputs` 15 | 8. Run app. 16 | 17 | ## File structure 18 | 19 | ``` 20 | assets 21 | └───font 22 | └───image 23 | └───2.0x 24 | └───3.0x 25 | 26 | libs 27 | └───common 28 | │ └───app_colors.dart 29 | │ └───app_dimens.dart 30 | │ └───app_images.dart 31 | │ └───app_shadows.dart 32 | │ └───app_text_styles.dart 33 | │ └───app_themes.dart 34 | └───configs 35 | │ └───app_configs.dart 36 | └───database 37 | │ └───secure_storage_helper.dart 38 | │ └───shared_preferences_helper.dart 39 | │ └─── ... 40 | └───l10n 41 | └───models 42 | │ └───entities 43 | │ │ └───user_entity.dart 44 | │ │ └─── ... 45 | │ └───enums 46 | │ │ └───load_status.dart 47 | │ │ └─── ... 48 | │ └───params 49 | │ │ └───sign_up_param.dart 50 | │ │ └─── ... 51 | │ └───response 52 | │ └───array_response.dart 53 | │ └───object_response.dart 54 | └───networks 55 | │ └───api_client.dart 56 | │ └───api_interceptors.dart 57 | │ └───api_util.dart 58 | └───router 59 | │ └───route_config.dart 60 | └───services 61 | │ └───api 62 | │ └───store 63 | │ └───auth_service.dart 64 | │ └───cache_service.dart 65 | │ └───setting_service.dart 66 | └───ui 67 | │ └───commons 68 | │ │ └───app_bottom_sheet.dart 69 | │ │ └───app_dialog.dart 70 | │ │ └───app_snackbar.dart 71 | │ │ └───... 72 | │ └───pages 73 | │ │ └───splash 74 | │ │ │ └───splash_logic.dart 75 | │ │ │ └───splash_state.dart 76 | │ │ │ └───splash_view.dart 77 | │ │ └───... 78 | │ └───widget 79 | │ └───appbar 80 | │ └───buttons 81 | │ │ └───app_button.dart 82 | │ │ └───app_icon_button.dart 83 | │ │ └───... 84 | │ └───images 85 | │ │ └───app_cache_image.dart 86 | │ │ └───app_circle_avatar.dart 87 | │ └───textfields 88 | │ └───shimmer 89 | │ └───... 90 | └───utils 91 | │ └───date_utils.dart 92 | │ └───file_utils.dart 93 | │ └───logger.dart 94 | │ └───utils.dart 95 | └───main.dart 96 | ``` 97 | ### main.dart 98 | The "entry point" of program. 99 | In general, `main.dart` contain **AppMaterial**, but this repo use **GetMaterialApp** whichs has the default MaterialApp as a child. 100 | ### assets 101 | This folder is to store static assests like fonts and images. 102 | ### common 103 | ### configs 104 | This folder hold the config of your applications. 105 | ### database 106 | ### l10n 107 | This folder contain all localized string. [See more](https://flutter.dev/docs/development/accessibility-and-localization/internationalization) 108 | ### models 109 | ### networks 110 | ### router 111 | This folder contain the route navigation 112 | ### services 113 | This folder contain all GetxService or any service which can not be removed from memory. 114 | ### ui 115 | ### utils 116 | 117 | ## How to use 118 | ### Creating a screen. 119 | All screen should be created in the `ui/pages` folder 120 | User the [GetX](https://github.com/CNAD666/getx_template) plugin to create new screen. 121 | #### Example: MovieSection 122 | **Logic:** `movies_section_logic.dart` 123 | ```java= 124 | class MoviesSectionLogic extends GetxController { 125 | final state = MoviesSectionState(); 126 | final apiService = Get.find(); 127 | 128 | void fetchInitialMovies() async { 129 | state.loadMovieStatus.value = LoadStatus.loading; 130 | try { 131 | final result = await apiService.getMovies(page: 1); 132 | state.loadMovieStatus.value = LoadStatus.success; 133 | state.movies.value = result.results; 134 | state.page.value = result.page; 135 | state.totalPages.value = result.totalPages; 136 | } catch (e) { 137 | state.loadMovieStatus.value = LoadStatus.failure; 138 | } 139 | } 140 | ... 141 | } 142 | ``` 143 | **State:** `movies_section_state.dart` 144 | ```java= 145 | class MoviesSectionState { 146 | final loadMovieStatus = LoadStatus.initial.obs; 147 | final movies = [].obs; 148 | final page = 1.obs; 149 | final totalResults = 0.obs; 150 | final totalPages = 0.obs; 151 | ... 152 | } 153 | ``` 154 | **View:** `movies_section_view.dart` 155 | ```java= 156 | class MoviesSectionPage extends StatefulWidget {...} 157 | 158 | class _MoviesSectionPageState extends State { 159 | final MoviesSectionLogic logic = Get.put(MoviesSectionLogic()); 160 | final MoviesSectionState state = Get.find().state; 161 | 162 | @override 163 | Widget build(BuildContext context) { 164 | return Obx(() { 165 | if (state.loadMovieStatus.value == LoadStatus.loading) { 166 | return _buildLoadingList(); 167 | } else if (state.loadMovieStatus.value == LoadStatus.failure) { 168 | return Container(); 169 | } else { 170 | return _buildSuccessList( 171 | state.movies, 172 | showLoadingMore: !state.hasReachedMax, 173 | ); 174 | } 175 | }); 176 | } 177 | } 178 | ``` 179 | 180 | ### Creating api service. 181 | 1. Create entity object in folder `lib/models/entities` 182 | Ex: `movie_entity.dart` 183 | ```java= 184 | import 'package:json_annotation/json_annotation.dart'; 185 | 186 | part 'movie_entity.g.dart'; 187 | 188 | @JsonSerializable() 189 | class MovieEntity { 190 | @JsonKey() 191 | String? title; 192 | ... 193 | 194 | factory MovieEntity.fromJson(Map json) => _$MovieEntityFromJson(json); 195 | Map toJson() => _$MovieEntityToJson(this); 196 | } 197 | ``` 198 | Class must have `@JsonSerializable()` for generator. Read [json_serializable](https://pub.dev/packages/json_serializable) 199 | 200 | 2. Define and Generate your API in file `lib/networks/api_client.dart` 201 | Ex: GET movies 202 | ```java= 203 | /// Movie 204 | @GET("/3/discover/movie") 205 | Future> getMovies(@Query('api_key') String apiKey, @Query('page') int page); 206 | ``` 207 | Note: Using **ArrayResponse** and **ObjectResponse** for generic response 208 | 209 | 3. Require run command line: 210 | ``` 211 | flutter pub run build_runner build --delete-conflicting-outputs 212 | ``` 213 | 214 | 4. Create api service file for your feature in folder `lib/services/api` 215 | Ex: `movies_api.dart` 216 | ```java= 217 | part of 'api_service.dart'; 218 | 219 | extension MovieApiService on ApiService { 220 | Future> getMovies({int page = 1}) async { 221 | return _apiClient.getMovies(MovieAPIConfig.APIKey, page); 222 | } 223 | } 224 | ``` 225 | After, add `part 'auth_api.dart';` to `services/api/api_service` 226 | 227 | 5. You can call API in the logic of screen. 228 | Ex: 229 | ```java= 230 | final apiService = Get.find(); 231 | final result = await apiService.getMovies(page: 1); 232 | ``` 233 | 234 | ### Support multiple Theme and Language 235 | See **SettingService** class for more detail 236 | ![](https://i.imgur.com/2DUnGpZ.png) 237 | 238 | 239 | ### Other 240 | #### Logger 241 | ```java= 242 | logger.d("message"); //"💙 DEBUG: message" 243 | logger.i("message"); //"💚 INFO: message" 244 | logger.e("message"); //"❤️ ERROR: message" 245 | logger.log("very very very long message"); 246 | ``` 247 | #### Snackbar 248 | ```java= 249 | AppSnackbar.showInfo(message: 'Info'); 250 | AppSnackbar.showWarning(message: 'Warning'); 251 | AppSnackbar.showError(message: 'Error'); 252 | ``` 253 | #### Dialog 254 | ```java= 255 | AppDialog.defaultDialog( 256 | message: "An error happened. Please check your connection!", 257 | textConfirm: "Retry", 258 | onConfirm: () { 259 | //Do something 260 | }, 261 | ); 262 | ``` 263 | #### Button UI when call API 264 | ```java= 265 | return Obx(() { 266 | return Container( 267 | padding: EdgeInsets.symmetric(horizontal: 20), 268 | child: AppTintButton( 269 | title: 'Sign In', 270 | onPressed: _signIn, 271 | isLoading: state.signInStatus.value == LoadStatus.loading, 272 | ), 273 | ); 274 | }); 275 | ``` 276 | 277 | ## Refer 278 | https://github.com/CNAD666/getx_template/blob/main/docs/Use%20of%20Flutter%20GetX---simple%20charm!.md 279 | https://pub.dev/documentation/get/latest/ 280 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | # Android Studio 3 in .gitignore file. 48 | .idea/caches 49 | .idea/modules.xml 50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 51 | .idea/navEditor.xml 52 | 53 | # Keystore files 54 | # Uncomment the following lines if you do not want to check your keystore files in. 55 | #*.jks 56 | #*.keystore 57 | 58 | # External native build folder generated in Android Studio 2.2 and later 59 | .externalNativeBuild 60 | .cxx/ 61 | 62 | # Google Services (e.g. APIs or Firebase) 63 | # google-services.json 64 | 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | 70 | # fastlane 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | fastlane/readme.md 76 | 77 | # Version control 78 | vcs.xml 79 | 80 | # lint 81 | lint/intermediates/ 82 | lint/generated/ 83 | lint/outputs/ 84 | lint/tmp/ 85 | # lint/reports/ 86 | 87 | # Android Profiling 88 | *.hprof -------------------------------------------------------------------------------- /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 31 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "vn.newwave.flutter_app" 38 | minSdkVersion 16 39 | targetSdkVersion 31 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | } 60 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/vn/newwave/flutter_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package vn.newwave.flutter_app 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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 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 | task clean(type: 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 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /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/fonts/Arimo-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/fonts/Arimo-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Arimo-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/fonts/Arimo-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Arimo-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/fonts/Arimo-Italic.ttf -------------------------------------------------------------------------------- /assets/fonts/Arimo-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/fonts/Arimo-Medium.ttf -------------------------------------------------------------------------------- /assets/fonts/Arimo-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/fonts/Arimo-MediumItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Arimo-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/fonts/Arimo-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/Arimo-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/fonts/Arimo-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/Arimo-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/fonts/Arimo-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /assets/images/2.0x/bg_image_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/2.0x/bg_image_placeholder.png -------------------------------------------------------------------------------- /assets/images/2.0x/ic_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/2.0x/ic_avatar.png -------------------------------------------------------------------------------- /assets/images/2.0x/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/2.0x/ic_back.png -------------------------------------------------------------------------------- /assets/images/2.0x/ic_eye_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/2.0x/ic_eye_close.png -------------------------------------------------------------------------------- /assets/images/2.0x/ic_eye_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/2.0x/ic_eye_open.png -------------------------------------------------------------------------------- /assets/images/2.0x/ic_image_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/2.0x/ic_image_placeholder.png -------------------------------------------------------------------------------- /assets/images/2.0x/ic_logo_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/2.0x/ic_logo_transparent.png -------------------------------------------------------------------------------- /assets/images/2.0x/ic_white_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/2.0x/ic_white_back.png -------------------------------------------------------------------------------- /assets/images/3.0x/bg_image_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/3.0x/bg_image_placeholder.png -------------------------------------------------------------------------------- /assets/images/3.0x/ic_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/3.0x/ic_avatar.png -------------------------------------------------------------------------------- /assets/images/3.0x/ic_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/3.0x/ic_back.png -------------------------------------------------------------------------------- /assets/images/3.0x/ic_eye_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/3.0x/ic_eye_close.png -------------------------------------------------------------------------------- /assets/images/3.0x/ic_eye_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/3.0x/ic_eye_open.png -------------------------------------------------------------------------------- /assets/images/3.0x/ic_image_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/3.0x/ic_image_placeholder.png -------------------------------------------------------------------------------- /assets/images/3.0x/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/3.0x/ic_logo.png -------------------------------------------------------------------------------- /assets/images/3.0x/ic_logo_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/3.0x/ic_logo_transparent.png -------------------------------------------------------------------------------- /assets/images/3.0x/ic_white_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/assets/images/3.0x/ic_white_back.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ -------------------------------------------------------------------------------- /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 | 9.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, '9.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 | - flutter_secure_storage (3.3.1): 4 | - Flutter 5 | - FMDB (2.7.5): 6 | - FMDB/standard (= 2.7.5) 7 | - FMDB/standard (2.7.5) 8 | - package_info_plus (0.4.5): 9 | - Flutter 10 | - path_provider_ios (0.0.1): 11 | - Flutter 12 | - shared_preferences_ios (0.0.1): 13 | - Flutter 14 | - sqflite (0.0.2): 15 | - Flutter 16 | - FMDB (>= 2.7.5) 17 | 18 | DEPENDENCIES: 19 | - Flutter (from `Flutter`) 20 | - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) 21 | - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) 22 | - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) 23 | - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) 24 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 25 | 26 | SPEC REPOS: 27 | trunk: 28 | - FMDB 29 | 30 | EXTERNAL SOURCES: 31 | Flutter: 32 | :path: Flutter 33 | flutter_secure_storage: 34 | :path: ".symlinks/plugins/flutter_secure_storage/ios" 35 | package_info_plus: 36 | :path: ".symlinks/plugins/package_info_plus/ios" 37 | path_provider_ios: 38 | :path: ".symlinks/plugins/path_provider_ios/ios" 39 | shared_preferences_ios: 40 | :path: ".symlinks/plugins/shared_preferences_ios/ios" 41 | sqflite: 42 | :path: ".symlinks/plugins/sqflite/ios" 43 | 44 | SPEC CHECKSUMS: 45 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a 46 | flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec 47 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 48 | package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e 49 | path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 50 | shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad 51 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 52 | 53 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 54 | 55 | COCOAPODS: 1.11.2 56 | -------------------------------------------------------------------------------- /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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newwavesolutions/flutter-app-getx/43c7815910271dce8483b1b5278fd42b7d1f8dad/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 | Flutter App 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | flutter_app 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 | 47 | 48 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/bindings/initial_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'repository_bindings.dart'; 4 | 5 | class InitialBinding implements Bindings { 6 | @override 7 | void dependencies() { 8 | RepositoryBindings().dependencies(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/bindings/repository_bindings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_app/networks/api_util.dart'; 2 | import 'package:flutter_app/repositories/auth_repository.dart'; 3 | import 'package:flutter_app/repositories/movie_repository.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | import '../repositories/notification_repository.dart'; 7 | 8 | class RepositoryBindings implements Bindings { 9 | @override 10 | void dependencies() { 11 | final apiClient = ApiUtil.getApiClient(); 12 | Get.lazyPut( 13 | () => AuthRepositoryImpl(apiClient: apiClient), 14 | tag: (AuthRepository).toString(), 15 | fenix: true, 16 | ); 17 | Get.lazyPut( 18 | () => MovieRepositoryImpl(apiClient: apiClient), 19 | fenix: true, 20 | ); 21 | Get.lazyPut( 22 | () => NotificationRepositoryImpl(apiClient: apiClient), 23 | fenix: true, 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/common/app_colors.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | class AppColors { 4 | ///Common 5 | static const Color primary = Color(0xFF1a222d); 6 | static const Color secondary = Color(0xFFd74315); 7 | static const Color accent = Color(0xFFd74315); 8 | 9 | ///Background 10 | static const Color background = Color(0xFF1a222d); 11 | static const Color backgroundLighter = Color(0xFF1f2837); 12 | static const Color backgroundDarker = Color(0xff111821); 13 | 14 | ///Shadow 15 | static const Color shadow = Color(0x25606060); 16 | 17 | ///Border 18 | static const Color border = Color(0xFF606060); 19 | 20 | ///Divider 21 | static const Color divider = Color(0xFF606060); 22 | 23 | ///Text 24 | static const Color textWhite = Color(0xFFFFFFFF); 25 | static const Color textBlack = Color(0xFF000000); 26 | static const Color textBlue = Color(0xFF0000FF); 27 | static const Color textDisable = Color(0xFF89a3b1); 28 | 29 | ///TextField 30 | static const Color textFieldEnabledBorder = Color(0xFF919191); 31 | static const Color textFieldFocusedBorder = Color(0xFFd74315); 32 | static const Color textFieldDisabledBorder = Color(0xFF919191); 33 | static const Color textFieldCursor = Color(0xFF919191); 34 | 35 | ///Button 36 | static const Color buttonBGWhite = Color(0xFFcdd0d5); 37 | static const Color buttonBGTint = Color(0xFFd74315); 38 | static const Color buttonBorder = Color(0xFFd74315); 39 | 40 | /// Tabs 41 | static const Color imageBG = Color(0xFF919191); 42 | 43 | ///BottomNavigationBar 44 | static const Color bottomNavigationBar = Color(0xFF919191); 45 | } 46 | -------------------------------------------------------------------------------- /lib/common/app_dimens.dart: -------------------------------------------------------------------------------- 1 | class AppDimens { 2 | AppDimens._(); // this basically makes it so you can instantiate this class 3 | //TextFontSize 4 | static const double fontSmaller = 11.0; 5 | static const double fontMaxSmall = 10.0; 6 | static const double fontSmall = 12.0; 7 | static const double fontNormal = 14.0; 8 | static const double fontLarge = 16.0; 9 | static const double fontExtra = 18.0; 10 | 11 | static const double buttonHeight = 48; 12 | static const double buttonCornerRadius = 8; 13 | static const double buttonBorderWidth = 1; 14 | 15 | static const double appBarHeight = 56; 16 | 17 | static const double paddingNormal = 20; 18 | static const double paddingSmall = 10; 19 | 20 | static const double marginNormal = 20; 21 | static const double marginLarge = 32; 22 | static const double marginSmall = 10; 23 | } 24 | -------------------------------------------------------------------------------- /lib/common/app_shadows.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'app_colors.dart'; 4 | 5 | class AppShadow { 6 | static final boxShadow = [ 7 | BoxShadow( 8 | color: AppColors.shadow, 9 | blurRadius: 3, 10 | offset: Offset(0, 0), 11 | ), 12 | ]; 13 | } 14 | -------------------------------------------------------------------------------- /lib/common/app_text_styles.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'app_colors.dart'; 6 | 7 | class AppTextStyle { 8 | ///Black 9 | static final black = TextStyle(color: Colors.black); 10 | 11 | //s12 12 | static final blackS12 = black.copyWith(fontSize: 12); 13 | static final blackS12Bold = blackS12.copyWith(fontWeight: FontWeight.bold); 14 | static final blackS12W800 = blackS12.copyWith(fontWeight: FontWeight.w800); 15 | 16 | //s14 17 | static final blackS14 = black.copyWith(fontSize: 14); 18 | static final blackS14Bold = blackS14.copyWith(fontWeight: FontWeight.bold); 19 | static final blackS14W800 = blackS14.copyWith(fontWeight: FontWeight.w800); 20 | 21 | //s16 22 | static final blackS16 = black.copyWith(fontSize: 14); 23 | static final blackS16Bold = blackS16.copyWith(fontWeight: FontWeight.bold); 24 | static final blackS16W800 = blackS16.copyWith(fontWeight: FontWeight.w800); 25 | 26 | //s18 27 | static final blackS18 = black.copyWith(fontSize: 14); 28 | static final blackS18Bold = blackS18.copyWith(fontWeight: FontWeight.bold); 29 | static final blackS18W800 = blackS18.copyWith(fontWeight: FontWeight.w800); 30 | 31 | ///White 32 | static final white = TextStyle(color: Colors.white); 33 | 34 | //s12 35 | static final whiteS12 = white.copyWith(fontSize: 12); 36 | static final whiteS12Bold = whiteS12.copyWith(fontWeight: FontWeight.bold); 37 | static final whiteS12W800 = whiteS12.copyWith(fontWeight: FontWeight.w800); 38 | 39 | //s14 40 | static final whiteS14 = white.copyWith(fontSize: 14); 41 | static final whiteS14Bold = whiteS14.copyWith(fontWeight: FontWeight.bold); 42 | static final whiteS14W800 = whiteS14.copyWith(fontWeight: FontWeight.w800); 43 | 44 | //s16 45 | static final whiteS16 = white.copyWith(fontSize: 16); 46 | static final whiteS16Bold = whiteS16.copyWith(fontWeight: FontWeight.bold); 47 | static final whiteS16W800 = whiteS16.copyWith(fontWeight: FontWeight.w800); 48 | 49 | //s18 50 | static final whiteS18 = white.copyWith(fontSize: 18); 51 | static final whiteS18Bold = whiteS18.copyWith(fontWeight: FontWeight.bold); 52 | static final whiteS18W800 = whiteS18.copyWith(fontWeight: FontWeight.w800); 53 | 54 | ///Gray 55 | static final grey = TextStyle(color: Colors.grey); 56 | 57 | //s12 58 | static final greyS12 = grey.copyWith(fontSize: 12); 59 | static final greyS12Bold = greyS12.copyWith(fontWeight: FontWeight.bold); 60 | static final greyS12W800 = greyS12.copyWith(fontWeight: FontWeight.w800); 61 | 62 | //s14 63 | static final greyS14 = grey.copyWith(fontSize: 14); 64 | static final greyS14Bold = greyS14.copyWith(fontWeight: FontWeight.bold); 65 | static final greyS14W800 = greyS14.copyWith(fontWeight: FontWeight.w800); 66 | 67 | //s16 68 | static final greyS16 = grey.copyWith(fontSize: 16); 69 | static final greyS16Bold = greyS16.copyWith(fontWeight: FontWeight.bold); 70 | static final greyS16W800 = greyS16.copyWith(fontWeight: FontWeight.w800); 71 | 72 | //s18 73 | static final greyS18 = grey.copyWith(fontSize: 18); 74 | static final greyS18Bold = greyS18.copyWith(fontWeight: FontWeight.bold); 75 | static final greyS18W800 = greyS18.copyWith(fontWeight: FontWeight.w800); 76 | 77 | ///Tint 78 | static final tint = TextStyle(color: AppColors.secondary); 79 | 80 | //s12 81 | static final tintS12 = tint.copyWith(fontSize: 12); 82 | static final tintS12Bold = tintS12.copyWith(fontWeight: FontWeight.bold); 83 | static final tintS12W800 = tintS12.copyWith(fontWeight: FontWeight.w800); 84 | 85 | //s14 86 | static final tintS14 = tint.copyWith(fontSize: 14); 87 | static final tintS14Bold = tintS14.copyWith(fontWeight: FontWeight.bold); 88 | static final tintS14W800 = tintS14.copyWith(fontWeight: FontWeight.w800); 89 | 90 | //s16 91 | static final tintS16 = tint.copyWith(fontSize: 16); 92 | static final tintS16Bold = tintS16.copyWith(fontWeight: FontWeight.bold); 93 | static final tintS16W800 = tintS16.copyWith(fontWeight: FontWeight.w800); 94 | 95 | //s18 96 | static final tintS18 = tint.copyWith(fontSize: 18); 97 | static final tintS18Bold = tintS18.copyWith(fontWeight: FontWeight.bold); 98 | static final tintS18W800 = tintS18.copyWith(fontWeight: FontWeight.w800); 99 | } 100 | -------------------------------------------------------------------------------- /lib/common/app_themes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppThemes { 4 | AppThemes._(); 5 | 6 | //Font 7 | static String lightFont = "Arimo"; 8 | static String darkFont = "Arimo"; 9 | 10 | //Primary 11 | static const Color _lightPrimaryColor = Color(0xffffffff); 12 | static const Color _darkPrimaryColor = Color(0xFF1a222d); 13 | 14 | //Secondary 15 | static const Color _lightSecondaryColor = Color(0xFFd74315); 16 | static const Color _darkSecondaryColor = Color(0xFFd74315); 17 | 18 | //Background 19 | static const Color _lightBackgroundColor = Color(0xffffffff); 20 | static const Color _darkBackgroundColor = Color(0xFF1a222d); 21 | 22 | //Text 23 | static const Color _lightTextColor = Color(0xff000000); 24 | static const Color _darkTextColor = Color(0xffffffff); 25 | 26 | //Border 27 | static const Color _lightBorderColor = Colors.grey; 28 | static const Color _darkBorderColor = Colors.grey; 29 | 30 | //Icon 31 | static const Color _lightIconColor = Color(0xff000000); 32 | static const Color _darkIconColor = Color(0xffffffff); 33 | 34 | //Text themes 35 | static final TextTheme _lightTextTheme = TextTheme( 36 | headline1: TextStyle(fontSize: 96.0, color: _lightTextColor), 37 | headline2: TextStyle(fontSize: 60.0, color: _lightTextColor), 38 | headline3: TextStyle(fontSize: 48.0, color: _lightTextColor), 39 | headline4: TextStyle(fontSize: 34.0, color: _lightTextColor), 40 | headline5: TextStyle(fontSize: 24.0, color: _lightTextColor), 41 | headline6: TextStyle(fontSize: 20.0, color: _lightTextColor, fontWeight: FontWeight.w500), 42 | subtitle1: TextStyle(fontSize: 16.0, color: _lightTextColor), 43 | subtitle2: TextStyle(fontSize: 14.0, color: _lightTextColor, fontWeight: FontWeight.w500), 44 | bodyText1: TextStyle(fontSize: 16.0, color: _lightTextColor), 45 | bodyText2: TextStyle(fontSize: 14.0, color: _lightTextColor), 46 | button: TextStyle(fontSize: 14.0, color: _lightTextColor, fontWeight: FontWeight.w500), 47 | caption: TextStyle(fontSize: 12.0, color: _lightTextColor), 48 | overline: TextStyle(fontSize: 14.0, color: _lightTextColor), 49 | ); 50 | 51 | static final TextTheme _darkTextTheme = TextTheme( 52 | headline1: TextStyle(fontSize: 96.0, color: _darkTextColor), 53 | headline2: TextStyle(fontSize: 60.0, color: _darkTextColor), 54 | headline3: TextStyle(fontSize: 48.0, color: _darkTextColor), 55 | headline4: TextStyle(fontSize: 34.0, color: _darkTextColor), 56 | headline5: TextStyle(fontSize: 24.0, color: _darkTextColor), 57 | headline6: TextStyle(fontSize: 20.0, color: _darkTextColor, fontWeight: FontWeight.w500), 58 | subtitle1: TextStyle(fontSize: 16.0, color: _darkTextColor), 59 | subtitle2: TextStyle(fontSize: 14.0, color: _darkTextColor, fontWeight: FontWeight.w500), 60 | bodyText1: TextStyle(fontSize: 16.0, color: _darkTextColor), 61 | bodyText2: TextStyle(fontSize: 14.0, color: _darkTextColor), 62 | button: TextStyle(fontSize: 14.0, color: _darkTextColor, fontWeight: FontWeight.w500), 63 | caption: TextStyle(fontSize: 12.0, color: _darkTextColor), 64 | overline: TextStyle(fontSize: 14.0, color: _darkTextColor), 65 | ); 66 | 67 | ///Light theme 68 | static final ThemeData lightTheme = ThemeData( 69 | brightness: Brightness.light, 70 | primaryColor: _lightPrimaryColor, 71 | accentColor: _lightSecondaryColor, 72 | fontFamily: darkFont, 73 | scaffoldBackgroundColor: _lightBackgroundColor, 74 | appBarTheme: AppBarTheme( 75 | color: _lightBackgroundColor, 76 | iconTheme: IconThemeData(color: _lightIconColor), 77 | textTheme: _lightTextTheme, 78 | ), 79 | iconTheme: IconThemeData( 80 | color: _lightIconColor, 81 | ), 82 | textTheme: _lightTextTheme, 83 | dividerTheme: DividerThemeData( 84 | color: Colors.grey, 85 | ), 86 | ); 87 | 88 | ///Dark theme 89 | static final ThemeData darkTheme = ThemeData( 90 | brightness: Brightness.dark, 91 | primaryColor: _darkPrimaryColor, 92 | accentColor: _darkSecondaryColor, 93 | fontFamily: darkFont, 94 | scaffoldBackgroundColor: _darkBackgroundColor, 95 | appBarTheme: AppBarTheme( 96 | color: _darkBackgroundColor, 97 | iconTheme: IconThemeData(color: _darkIconColor), 98 | textTheme: _darkTextTheme, 99 | ), 100 | iconTheme: IconThemeData( 101 | color: _darkIconColor, 102 | ), 103 | textTheme: _darkTextTheme, 104 | dividerTheme: DividerThemeData( 105 | color: Colors.grey, 106 | ), 107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /lib/configs/app_configs.dart: -------------------------------------------------------------------------------- 1 | class AppConfigs { 2 | static const String appName = 'Real Agent'; 3 | 4 | ///DEV 5 | 6 | ///STAGING 7 | static const envName = "Staging"; 8 | static const webUrl = "https://www.themoviedb.org"; 9 | static const baseUrl = "https://api.themoviedb.org"; 10 | static const socketUrl = 'wss://socket.themoviedb.org'; //Todo: change this 11 | 12 | ///PRODUCTION 13 | 14 | ///Paging 15 | static const pageSize = 20; 16 | static const pageSizeMax = 1000; 17 | 18 | ///Local 19 | static const appLocal = 'vi_VN'; 20 | static const appLanguage = 'en'; 21 | 22 | ///DateFormat 23 | 24 | static const dateAPIFormat = 'dd/MM/yyyy'; 25 | static const dateDisplayFormat = 'dd/MM/yyyy'; 26 | 27 | static const dateTimeAPIFormat = "MM/dd/yyyy'T'hh:mm:ss.SSSZ"; //Use DateTime.parse(date) instead of ... 28 | static const dateTimeDisplayFormat = 'dd/MM/yyyy HH:mm'; 29 | 30 | ///Date range 31 | static final identityMinDate = DateTime(1900, 1, 1); 32 | static final identityMaxDate = DateTime.now(); 33 | static final birthMinDate = DateTime(1900, 1, 1); 34 | static final birthMaxDate = DateTime.now(); 35 | 36 | ///Font 37 | static const fontFamily = 'Roboto'; 38 | 39 | ///Max file 40 | static const maxAttachFile = 5; 41 | } 42 | 43 | class FirebaseConfig { 44 | //Todo 45 | } 46 | 47 | class DatabaseConfig { 48 | //Todo 49 | static const int version = 1; 50 | } 51 | 52 | class MovieAPIConfig { 53 | static const String APIKey = '26763d7bf2e94098192e629eb975dab0'; 54 | } 55 | 56 | class UpGraderAPIConfig { 57 | static const String APIKey = ''; 58 | } 59 | -------------------------------------------------------------------------------- /lib/database/secure_storage_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter_app/model/entities/index.dart'; 4 | import 'package:flutter_secure_storage/flutter_secure_storage.dart'; 5 | 6 | import 'share_preferences_helper.dart'; 7 | 8 | class SecureStorageHelper { 9 | static const _apiTokenKey = '_apiTokenKey'; 10 | 11 | final FlutterSecureStorage _storage; 12 | 13 | SecureStorageHelper._internal(this._storage); 14 | 15 | static final SecureStorageHelper _singleton = SecureStorageHelper._internal(FlutterSecureStorage()); 16 | 17 | factory SecureStorageHelper() { 18 | return _singleton; 19 | } 20 | 21 | factory SecureStorageHelper.getInstance() { 22 | return _singleton; 23 | } 24 | 25 | //Save token 26 | void saveToken(TokenEntity token) async { 27 | await _storage.write(key: _apiTokenKey, value: jsonEncode(token.toJson())); 28 | SharedPreferencesHelper.setApiTokenKey(_apiTokenKey); 29 | } 30 | 31 | //Remove token 32 | void removeToken() async { 33 | await _storage.delete(key: _apiTokenKey); 34 | SharedPreferencesHelper.removeApiTokenKey(); 35 | } 36 | 37 | //Get token 38 | Future getToken() async { 39 | try { 40 | final key = await SharedPreferencesHelper.getApiTokenKey(); 41 | final tokenEncoded = await _storage.read(key: key); 42 | if (tokenEncoded == null) { 43 | return null; 44 | } else { 45 | return TokenEntity.fromJson(jsonDecode(tokenEncoded) as Map); 46 | } 47 | } catch (e) { 48 | return null; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/database/share_preferences_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_app/utils/logger.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | class SharedPreferencesHelper { 5 | static const _token = '_token'; 6 | static const _introKey = '_introKey'; 7 | 8 | static const _authKey = '_authKey'; 9 | 10 | //Get authKey 11 | static Future getApiTokenKey() async { 12 | try { 13 | SharedPreferences prefs = await SharedPreferences.getInstance(); 14 | return prefs.getString(_authKey) ?? ""; 15 | } catch (e) { 16 | logger.e(e); 17 | return ""; 18 | } 19 | } 20 | 21 | //Set authKey 22 | static void setApiTokenKey(String apiTokenKey) async { 23 | SharedPreferences prefs = await SharedPreferences.getInstance(); 24 | await prefs.setString(_authKey, apiTokenKey); 25 | } 26 | 27 | static void removeApiTokenKey() async { 28 | SharedPreferences prefs = await SharedPreferences.getInstance(); 29 | await prefs.remove(_authKey); 30 | } 31 | 32 | //Get intro 33 | static Future isSeenIntro() async { 34 | try { 35 | SharedPreferences prefs = await SharedPreferences.getInstance(); 36 | return prefs.getBool(_introKey) ?? false; 37 | } catch (e) { 38 | logger.e(e); 39 | return false; 40 | } 41 | } 42 | 43 | //Set intro 44 | static void setSeenIntro({isSeen = true}) async { 45 | SharedPreferences prefs = await SharedPreferences.getInstance(); 46 | await prefs.setBool(_introKey, isSeen ?? true); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/l10n/intl_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "settings_title": "Settings", 3 | "settings_themeMode": "Theme", 4 | "settings_themeModeSystem": "System", 5 | "settings_themeModeLight": "Light", 6 | "settings_themeModeDark": "Dark", 7 | "settings_languageVietnamese": "Tiếng việt", 8 | "settings_languageEnglish": "English", 9 | "settings_language": "Language" 10 | } -------------------------------------------------------------------------------- /lib/l10n/intl_vi.arb: -------------------------------------------------------------------------------- 1 | { 2 | "settings_title": "Cài đặt", 3 | "settings_themeMode": "Chủ đề", 4 | "settings_themeModeSystem": "Hệ thống", 5 | "settings_themeModeLight": "Sáng", 6 | "settings_themeModeDark": "Tối", 7 | "settings_languageVietnamese": "Tiếng việt", 8 | "settings_languageEnglish": "English", 9 | "settings_language": "Ngôn ngữ" 10 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/generated/l10n.dart'; 3 | import 'package:flutter_localizations/flutter_localizations.dart'; 4 | import 'package:get/get.dart'; 5 | import 'bindings/initial_binding.dart'; 6 | import 'common/app_themes.dart'; 7 | import 'router/route_config.dart'; 8 | import 'services/app_service.dart'; 9 | import 'services/setting_service.dart'; 10 | import 'ui/pages/splash/splash_view.dart'; 11 | 12 | void main() async { 13 | WidgetsFlutterBinding.ensureInitialized(); 14 | 15 | /// AWAIT SERVICES INITIALIZATION. 16 | await initServices(); 17 | 18 | runApp(MyApp()); 19 | } 20 | 21 | /// Is a smart move to make your Services intiialize before you run the Flutter app. 22 | /// as you can control the execution flow (maybe you need to load some Theme configuration, 23 | /// apiKey, language defined by the User... so load SettingService before running ApiService. 24 | /// so GetMaterialApp() doesnt have to rebuild, and takes the values directly. 25 | Future initServices() async { 26 | /// Here is where you put get_storage, hive, shared_pref initialization. 27 | /// or moor connection, or whatever that's async. 28 | await Get.putAsync(() => SettingService().init()); 29 | await Get.putAsync(() => AppService().init()); 30 | } 31 | 32 | class MyApp extends StatefulWidget { 33 | @override 34 | State createState() { 35 | return _MyAppState(); 36 | } 37 | } 38 | 39 | class _MyAppState extends State { 40 | @override 41 | void initState() { 42 | super.initState(); 43 | } 44 | 45 | @override 46 | void dispose() { 47 | super.dispose(); 48 | } 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return GestureDetector( 53 | onTap: hideKeyboard, 54 | child: GetMaterialApp( 55 | home: SplashPage(), 56 | theme: AppThemes.lightTheme, 57 | darkTheme: AppThemes.darkTheme, 58 | themeMode: ThemeMode.system, 59 | initialBinding: InitialBinding(), 60 | initialRoute: RouteConfig.splash, 61 | getPages: RouteConfig.getPages, 62 | localizationsDelegates: [ 63 | GlobalMaterialLocalizations.delegate, 64 | GlobalWidgetsLocalizations.delegate, 65 | GlobalCupertinoLocalizations.delegate, 66 | S.delegate, 67 | ], 68 | locale: Get.find().currentLocate.value, 69 | supportedLocales: S.delegate.supportedLocales, 70 | ), 71 | ); 72 | } 73 | 74 | void hideKeyboard() { 75 | FocusScopeNode currentFocus = FocusScope.of(context); 76 | if (!currentFocus.hasPrimaryFocus && currentFocus.focusedChild != null) { 77 | FocusManager.instance.primaryFocus?.unfocus(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/model/entities/index.dart: -------------------------------------------------------------------------------- 1 | export 'token_entity.dart'; 2 | export 'user/user_entity.dart'; 3 | export 'notification/notification_entity.dart'; 4 | export 'movie_entity.dart'; 5 | -------------------------------------------------------------------------------- /lib/model/entities/movie_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'movie_entity.g.dart'; 4 | 5 | @JsonSerializable() 6 | class MovieEntity { 7 | @JsonKey() 8 | String? title; 9 | @JsonKey(name: 'poster_path') 10 | String? posterPath; 11 | @JsonKey(name: 'backdrop_path') 12 | String? backdropPath; 13 | @JsonKey(name: 'vote_average') 14 | double? voteAverage; 15 | @JsonKey() 16 | String? overview; 17 | @JsonKey(name: 'release_date') 18 | String? releaseDate; 19 | 20 | MovieEntity({ 21 | this.title, 22 | this.posterPath, 23 | this.backdropPath, 24 | this.voteAverage, 25 | this.overview, 26 | this.releaseDate, 27 | }); 28 | 29 | factory MovieEntity.fromJson(Map json) => _$MovieEntityFromJson(json); 30 | 31 | Map toJson() => _$MovieEntityToJson(this); 32 | 33 | @JsonKey(ignore: true) 34 | String get posterUrl { 35 | return 'https://image.tmdb.org/t/p/w185' + (posterPath ?? ""); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/model/entities/notification/notification_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'notification_entity.g.dart'; 4 | 5 | @JsonSerializable() 6 | class NotificationEntity { 7 | @JsonKey() 8 | bool isRead; 9 | @JsonKey() 10 | String id; 11 | @JsonKey() 12 | String title; 13 | @JsonKey() 14 | String message; 15 | @JsonKey() 16 | String type; 17 | 18 | NotificationEntity({ 19 | this.isRead = false, 20 | this.id = "", 21 | this.title = "", 22 | this.message = "", 23 | this.type = "", 24 | }); 25 | 26 | factory NotificationEntity.fromJson(Map json) => _$NotificationEntityFromJson(json); 27 | 28 | Map toJson() => _$NotificationEntityToJson(this); 29 | } -------------------------------------------------------------------------------- /lib/model/entities/token_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'token_entity.g.dart'; 4 | 5 | @JsonSerializable() 6 | class TokenEntity { 7 | @JsonKey() 8 | String accessToken; 9 | @JsonKey() 10 | String refreshToken; 11 | 12 | TokenEntity({ 13 | this.accessToken = "", 14 | this.refreshToken = "", 15 | }); 16 | 17 | factory TokenEntity.fromJson(Map json) => _$TokenEntityFromJson(json); 18 | 19 | Map toJson() => _$TokenEntityToJson(this); 20 | } 21 | -------------------------------------------------------------------------------- /lib/model/entities/user/user_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'user_entity.g.dart'; 4 | 5 | @JsonSerializable() 6 | class UserEntity { 7 | @JsonKey() 8 | int? id; 9 | @JsonKey() 10 | String? username; 11 | @JsonKey() 12 | DateTime? birthday; 13 | @JsonKey() 14 | String? avatarUrl; 15 | 16 | UserEntity({ 17 | this.id, 18 | this.username, 19 | this.birthday, 20 | this.avatarUrl, 21 | }); 22 | 23 | factory UserEntity.mockData() { 24 | return UserEntity( 25 | id: 12345678, 26 | username: "Newwave", 27 | birthday: DateTime.now(), 28 | avatarUrl: "https://i.imgur.com/geqAiJG.jpg", 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/model/enums/gender_type.dart: -------------------------------------------------------------------------------- 1 | enum GenderType { 2 | male, 3 | female, 4 | } 5 | 6 | extension GenderTypeExtension on GenderType { 7 | static GenderType? fromString(String text) { 8 | if (text == "male" || text == "Nam") { 9 | return GenderType.male; 10 | } else if (text == "female" || text == "Nữ") { 11 | return GenderType.female; 12 | } 13 | return null; 14 | } 15 | 16 | String get vnText { 17 | switch (this) { 18 | case GenderType.male: 19 | return 'Nam'; 20 | case GenderType.female: 21 | return 'Nữ'; 22 | default: 23 | return ''; 24 | } 25 | } 26 | 27 | String get enText { 28 | switch (this) { 29 | case GenderType.male: 30 | return 'Male'; 31 | case GenderType.female: 32 | return 'Female'; 33 | default: 34 | return ''; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/model/enums/load_status.dart: -------------------------------------------------------------------------------- 1 | enum LoadStatus { 2 | initial, 3 | loading, 4 | success, 5 | failure, 6 | loadingMore, 7 | } 8 | -------------------------------------------------------------------------------- /lib/model/params/index.dart: -------------------------------------------------------------------------------- 1 | export 'sign_up_param.dart'; -------------------------------------------------------------------------------- /lib/model/params/sign_up_param.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'sign_up_param.g.dart'; 4 | 5 | @JsonSerializable() 6 | class SignUpParam { 7 | @JsonKey() 8 | final String? email; 9 | @JsonKey() 10 | final String? name; 11 | @JsonKey() 12 | final String? password; 13 | @JsonKey() 14 | final String? phone; 15 | 16 | SignUpParam({ 17 | this.email, 18 | this.name, 19 | this.password, 20 | this.phone, 21 | }); 22 | 23 | factory SignUpParam.fromJson(Map json) => _$SignUpParamFromJson(json); 24 | 25 | Map toJson() => _$SignUpParamToJson(this); 26 | } 27 | -------------------------------------------------------------------------------- /lib/model/response/array_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'array_response.g.dart'; 4 | 5 | @JsonSerializable(genericArgumentFactories: true) 6 | class ArrayResponse { 7 | @JsonKey(defaultValue: 1) 8 | final int page; 9 | @JsonKey(name: "total_pages", defaultValue: 0) 10 | final int totalPages; 11 | @JsonKey(name: "total_results", defaultValue: 0) 12 | final int totalResults; 13 | @JsonKey(defaultValue: []) 14 | final List results; 15 | 16 | ArrayResponse({ 17 | this.page = 1, 18 | this.totalPages = 0, 19 | this.totalResults = 0, 20 | this.results = const [], 21 | }); 22 | 23 | factory ArrayResponse.fromJson(Map json, T Function(Object? json) fromJsonT) => _$ArrayResponseFromJson(json, fromJsonT); 24 | 25 | Map toJson(Object? Function(T value) toJsonT) => _$ArrayResponseToJson(this, toJsonT); 26 | } 27 | -------------------------------------------------------------------------------- /lib/model/response/object_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'object_response.g.dart'; 4 | 5 | @JsonSerializable(genericArgumentFactories: true) 6 | class ObjectResponse { 7 | @JsonKey(defaultValue: "") 8 | final String message; 9 | @JsonKey() 10 | final T? data; 11 | 12 | ObjectResponse({ 13 | this.message = "", 14 | this.data, 15 | }); 16 | 17 | factory ObjectResponse.fromJson(Map json, T Function(Object? json) fromJsonT) => _$ObjectResponseFromJson(json, fromJsonT); 18 | 19 | Map toJson(Object? Function(T value) toJsonT) => _$ObjectResponseToJson(this, toJsonT); 20 | } 21 | -------------------------------------------------------------------------------- /lib/networks/api_client.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_app/configs/app_configs.dart'; 3 | import 'package:flutter_app/model/entities/index.dart'; 4 | import 'package:flutter_app/model/response/array_response.dart'; 5 | import 'package:retrofit/retrofit.dart'; 6 | 7 | part 'api_client.g.dart'; 8 | 9 | @RestApi(baseUrl: AppConfigs.baseUrl) 10 | abstract class ApiClient { 11 | factory ApiClient(Dio dio, {String baseUrl}) = _ApiClient; 12 | 13 | ///User 14 | @POST("/login") 15 | Future authLogin(@Body() Map body); 16 | 17 | @POST("/logout") 18 | Future signOut(@Body() Map body); 19 | 20 | /// Notification 21 | @GET("/notifications") 22 | Future> getNotifications( 23 | @Query('page') int page, 24 | @Query('pageSize') int pageSize, 25 | ); 26 | 27 | /// Movie 28 | @GET("/3/discover/movie") 29 | Future> getMovies(@Query('api_key') String apiKey, @Query('page') int page); 30 | } 31 | -------------------------------------------------------------------------------- /lib/networks/api_interceptors.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:flutter_app/repositories/auth_repository.dart'; 5 | import 'package:flutter_app/ui/pages/sign_in/sign_in_view.dart'; 6 | import 'package:flutter_app/utils/logger.dart'; 7 | import 'package:get/get.dart' hide Response; 8 | 9 | class ApiInterceptors extends InterceptorsWrapper { 10 | @override 11 | void onRequest( 12 | RequestOptions options, RequestInterceptorHandler handler) async { 13 | final method = options.method; 14 | final uri = options.uri; 15 | final data = options.data; 16 | final authRepository = Get.find(tag: (AuthRepository).toString()); 17 | final token = await authRepository.getToken(); 18 | if (token != null) { 19 | options.headers['Authorization'] = 'Bearer ${token.accessToken}'; 20 | } 21 | apiLogger.log( 22 | "\n\n--------------------------------------------------------------------------------------------------------"); 23 | if (method == 'GET') { 24 | apiLogger.log( 25 | "✈️ REQUEST[$method] => PATH: $uri \n Token: ${options.headers}", 26 | printFullText: true); 27 | } else { 28 | try { 29 | apiLogger.log( 30 | "✈️ REQUEST[$method] => PATH: $uri \n Token: ${token?.accessToken} \n DATA: ${jsonEncode(data)}", 31 | printFullText: true); 32 | } catch (e) { 33 | apiLogger.log( 34 | "✈️ REQUEST[$method] => PATH: $uri \n Token: ${token?.accessToken} \n DATA: $data", 35 | printFullText: true); 36 | } 37 | } 38 | super.onRequest(options, handler); 39 | } 40 | 41 | @override 42 | void onResponse(Response response, ResponseInterceptorHandler handler) { 43 | final statusCode = response.statusCode; 44 | final uri = response.requestOptions.uri; 45 | final data = jsonEncode(response.data); 46 | apiLogger.log("✅ RESPONSE[$statusCode] => PATH: $uri\n DATA: $data"); 47 | //Handle section expired 48 | if (response.statusCode == 401) { 49 | final authRepository = Get.find(tag: (AuthRepository).toString()); 50 | authRepository.signOut(); 51 | Get.off(SignInPage()); 52 | } 53 | super.onResponse(response, handler); 54 | } 55 | 56 | @override 57 | void onError(DioError err, ErrorInterceptorHandler handler) { 58 | final statusCode = err.response?.statusCode; 59 | final uri = err.requestOptions.path; 60 | var data = ""; 61 | try { 62 | data = jsonEncode(err.response?.data); 63 | } catch (e) {} 64 | apiLogger.log("⚠️ ERROR[$statusCode] => PATH: $uri\n DATA: $data"); 65 | super.onError(err, handler); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/networks/api_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_app/configs/app_configs.dart'; 3 | 4 | import 'api_client.dart'; 5 | import 'api_interceptors.dart'; 6 | 7 | class ApiUtil { 8 | static Dio? dio; 9 | 10 | static Dio getDio() { 11 | if (dio == null) { 12 | dio = Dio(); 13 | dio!.options.connectTimeout = 60000; 14 | dio!.interceptors.add(ApiInterceptors()); 15 | } 16 | return dio!; 17 | } 18 | 19 | static ApiClient getApiClient() { 20 | final apiClient = ApiClient(getDio(), baseUrl: AppConfigs.baseUrl); 21 | return apiClient; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/repositories/auth_repository.dart: -------------------------------------------------------------------------------- 1 | import '../database/secure_storage_helper.dart'; 2 | import '../model/entities/token_entity.dart'; 3 | import '../model/entities/user/user_entity.dart'; 4 | import '../networks/api_client.dart'; 5 | 6 | abstract class AuthRepository { 7 | Future signIn(String username, String password); 8 | 9 | Future getProfile(); 10 | 11 | void saveToken(TokenEntity token); 12 | 13 | Future getToken(); 14 | 15 | void removeToken(); 16 | 17 | Future isLoggedIn(); 18 | 19 | void signOut(); 20 | } 21 | 22 | class AuthRepositoryImpl extends AuthRepository { 23 | late ApiClient _apiClient; 24 | 25 | AuthRepositoryImpl({required ApiClient apiClient}) { 26 | _apiClient = apiClient; 27 | } 28 | 29 | Future signIn(String username, String password) async { 30 | await Future.delayed(Duration(seconds: 2)); 31 | return TokenEntity( 32 | accessToken: 'app_access_token', refreshToken: 'app_refresh_token'); 33 | } 34 | 35 | Future getProfile() async { 36 | await Future.delayed(Duration(seconds: 2)); 37 | //Todo: Mock data 38 | return UserEntity.mockData(); 39 | } 40 | 41 | /// Handle save/remove Token 42 | void saveToken(TokenEntity token) { 43 | return SecureStorageHelper.getInstance().saveToken(token); 44 | } 45 | 46 | void removeToken() { 47 | return SecureStorageHelper.getInstance().removeToken(); 48 | } 49 | 50 | @override 51 | Future getToken() { 52 | return SecureStorageHelper.getInstance().getToken(); 53 | } 54 | 55 | /// User 56 | // void updateUser(UserEntity user) { 57 | // this.user.value = user; 58 | // } 59 | // 60 | // void deleteUser() { 61 | // this.user.value = null; 62 | // } 63 | 64 | @override 65 | Future isLoggedIn() async { 66 | final token = await SecureStorageHelper.getInstance().getToken(); 67 | return token != null; 68 | } 69 | 70 | /// SignOut 71 | void signOut() async { 72 | removeToken(); 73 | // deleteUser(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/repositories/movie_repository.dart: -------------------------------------------------------------------------------- 1 | import '../configs/app_configs.dart'; 2 | import '../model/entities/movie_entity.dart'; 3 | import '../model/response/array_response.dart'; 4 | import '../networks/api_client.dart'; 5 | 6 | abstract class MovieRepository { 7 | Future> getMovies({int page = 1}); 8 | } 9 | 10 | class MovieRepositoryImpl extends MovieRepository { 11 | late ApiClient _apiClient; 12 | 13 | MovieRepositoryImpl({required ApiClient apiClient}) { 14 | _apiClient = apiClient; 15 | } 16 | 17 | Future> getMovies({int page = 1}) async { 18 | return _apiClient.getMovies(MovieAPIConfig.APIKey, page); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/repositories/notification_repository.dart: -------------------------------------------------------------------------------- 1 | import '../configs/app_configs.dart'; 2 | import '../model/entities/notification/notification_entity.dart'; 3 | import '../model/response/array_response.dart'; 4 | import '../networks/api_client.dart'; 5 | 6 | abstract class NotificationRepository { 7 | Future> getNotifications({ 8 | int page = 1, 9 | int pageSize = AppConfigs.pageSizeMax, 10 | }); 11 | 12 | Future markRead(String id); 13 | 14 | Future markReadAll(); 15 | } 16 | 17 | class NotificationRepositoryImpl extends NotificationRepository { 18 | late ApiClient _apiClient; 19 | 20 | NotificationRepositoryImpl({required ApiClient apiClient}) { 21 | _apiClient = apiClient; 22 | } 23 | 24 | Future> getNotifications( 25 | {int page = 1, int pageSize = AppConfigs.pageSizeMax}) { 26 | return _apiClient.getNotifications(page, pageSize); 27 | } 28 | 29 | Future markRead(String id) { 30 | // TODO: implement markRead 31 | throw UnimplementedError(); 32 | } 33 | 34 | Future markReadAll() { 35 | // TODO: implement markReadAll 36 | throw UnimplementedError(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/repositories/user_repository.dart: -------------------------------------------------------------------------------- 1 | import '../model/entities/token_entity.dart'; 2 | import '../model/entities/user/user_entity.dart'; 3 | import '../networks/api_client.dart'; 4 | 5 | abstract class AuthRepository { 6 | Future signIn(String username, String password); 7 | 8 | Future getProfile(); 9 | } 10 | 11 | class AuthRepositoryImpl extends AuthRepository { 12 | late ApiClient _apiClient; 13 | 14 | AuthRepositoryImpl({required ApiClient apiClient}) { 15 | _apiClient = apiClient; 16 | } 17 | 18 | Future signIn(String username, String password) async { 19 | await Future.delayed(Duration(seconds: 2)); 20 | return TokenEntity( 21 | accessToken: 'app_access_token', refreshToken: 'app_refresh_token'); 22 | } 23 | 24 | Future getProfile() async { 25 | await Future.delayed(Duration(seconds: 2)); 26 | //Todo: Mock data 27 | return UserEntity.mockData(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/router/route_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_app/ui/pages/main/main_view.dart'; 2 | import 'package:flutter_app/ui/pages/splash/splash_view.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | class RouteConfig { 6 | ///main page 7 | static final String splash = "/splash"; 8 | static final String main = "/main"; 9 | 10 | ///Alias ​​mapping page 11 | static final List getPages = [ 12 | GetPage(name: splash, page: () => SplashPage()), 13 | GetPage(name: main, page: () => MainPage()), 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /lib/services/app_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_app/generated/l10n.dart'; 5 | import 'package:flutter_app/model/entities/index.dart'; 6 | import 'package:get/get.dart'; 7 | import 'package:shared_preferences/shared_preferences.dart'; 8 | 9 | class AppService extends GetxService { 10 | // Theme 11 | final Rx user = null.obs; 12 | 13 | Future init() async { 14 | return this; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/services/setting_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_app/generated/l10n.dart'; 5 | import 'package:get/get.dart'; 6 | import 'package:shared_preferences/shared_preferences.dart'; 7 | 8 | extension ThemeModeExtension on ThemeMode { 9 | String get code { 10 | switch (this) { 11 | case ThemeMode.light: 12 | return 'light'; 13 | case ThemeMode.dark: 14 | return 'dark'; 15 | default: 16 | return 'system'; 17 | } 18 | } 19 | 20 | static ThemeMode fromCode(String code) { 21 | switch (code) { 22 | case 'light': 23 | return ThemeMode.light; 24 | case 'dark': 25 | return ThemeMode.dark; 26 | default: 27 | return ThemeMode.system; 28 | } 29 | } 30 | } 31 | 32 | class SettingService extends GetxService { 33 | // Theme 34 | final Rx currentThemeMode = ThemeMode.system.obs; 35 | 36 | // Language 37 | final Rx currentLocate = window.locale.obs; 38 | 39 | late SharedPreferences prefs; 40 | 41 | Future init() async { 42 | prefs = await SharedPreferences.getInstance(); 43 | 44 | ///ThemeMode 45 | String themeModeCode = prefs.getString("themeModeCode") ?? ThemeMode.system.code; 46 | final themeMode = ThemeModeExtension.fromCode(themeModeCode); 47 | currentThemeMode.value = themeMode; 48 | Get.changeThemeMode(themeMode); 49 | 50 | ///Language 51 | String languageCode = prefs.getString("languageCode") ?? window.locale.languageCode; 52 | var locale = S.delegate.supportedLocales.firstWhere( 53 | (element) => element.languageCode == languageCode, 54 | orElse: () => Locale.fromSubtags(languageCode: "en"), 55 | ); 56 | currentLocate.value = locale; 57 | Get.updateLocale(locale); 58 | 59 | return this; 60 | } 61 | 62 | void changeThemeMode(ThemeMode themeMode) async { 63 | prefs.setString('themeModeCode', themeMode.code); 64 | currentThemeMode.value = themeMode; 65 | Get.changeThemeMode(themeMode); 66 | } 67 | 68 | void updateLocale(Locale locale) async { 69 | var newLocale = S.delegate.supportedLocales.firstWhere( 70 | (element) => element.languageCode == locale.languageCode, 71 | orElse: () => Locale.fromSubtags(languageCode: "en"), 72 | ); 73 | prefs.setString('languageCode', newLocale.languageCode); 74 | currentLocate.value = newLocale; 75 | Get.updateLocale(newLocale); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/ui/commons/app_bottom_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | class AppBottomSheet { 5 | static void show(Widget bottomSheet) { 6 | Get.bottomSheet(bottomSheet); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/ui/commons/app_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | class AppDialog { 6 | static void defaultDialog({ 7 | String title = "Alert", 8 | String message = "", 9 | String? textConfirm, 10 | String? textCancel, 11 | VoidCallback? onConfirm, 12 | VoidCallback? onCancel, 13 | }) { 14 | Get.defaultDialog( 15 | title: title, 16 | content: Text( 17 | message, 18 | textAlign: TextAlign.center, 19 | ), 20 | onConfirm: onConfirm == null 21 | ? null 22 | : () { 23 | Get.back(); 24 | onConfirm.call(); 25 | }, 26 | onCancel: onCancel == null 27 | ? null 28 | : () { 29 | Get.back(); 30 | onCancel.call(); 31 | }, 32 | textConfirm: textConfirm, 33 | textCancel: textCancel, 34 | ); 35 | } 36 | 37 | static void showDatePicker( 38 | BuildContext context, { 39 | DateTime? minTime, 40 | DateTime? maxTime, 41 | DateChangedCallback? onConfirm, 42 | locale: LocaleType.en, 43 | DateTime? currentTime, 44 | }) { 45 | DatePicker.showDatePicker( 46 | context, 47 | minTime: minTime, 48 | maxTime: maxTime, 49 | onConfirm: onConfirm, 50 | locale: LocaleType.vi, 51 | currentTime: currentTime, 52 | theme: DatePickerTheme(), 53 | ); 54 | } 55 | 56 | static void showDateTimePicker( 57 | BuildContext context, { 58 | DateTime? minTime, 59 | DateTime? maxTime, 60 | DateChangedCallback? onConfirm, 61 | locale: LocaleType.en, 62 | DateTime? currentTime, 63 | }) { 64 | DatePicker.showDateTimePicker( 65 | context, 66 | minTime: minTime, 67 | maxTime: maxTime, 68 | onConfirm: onConfirm, 69 | locale: LocaleType.vi, 70 | currentTime: currentTime, 71 | theme: DatePickerTheme(), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/ui/commons/app_snackbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | class AppSnackbar { 5 | static void showInfo({String? title, String? message}) { 6 | Get.snackbar( 7 | title ?? "Info", 8 | message ?? "Empty message", 9 | backgroundColor: Colors.white, 10 | colorText: Colors.black, 11 | ); 12 | } 13 | 14 | static void showWarning({String? title, String? message}) { 15 | Get.snackbar( 16 | title ?? "Warning", 17 | message ?? "Empty message", 18 | backgroundColor: Colors.amber, 19 | colorText: Colors.white, 20 | ); 21 | } 22 | 23 | static void showError({String? title, String? message}) { 24 | Get.snackbar( 25 | title ?? "Error", 26 | message ?? "Empty message", 27 | backgroundColor: Colors.red, 28 | colorText: Colors.white, 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/ui/pages/forgot_password/forgot_password_cubit.dart: -------------------------------------------------------------------------------- 1 | // import 'package:bloc/bloc.dart'; 2 | // import 'package:equatable/equatable.dart'; 3 | // 4 | // part 'forgot_password_state.dart'; 5 | // 6 | // class ForgotPasswordCubit extends Cubit { 7 | // ForgotPasswordCubit() : super(ForgotPasswordInitial()); 8 | // } 9 | -------------------------------------------------------------------------------- /lib/ui/pages/forgot_password/forgot_password_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_app/common/app_colors.dart'; 4 | 5 | class ForgotPasswordPage extends StatefulWidget { 6 | @override 7 | State createState() { 8 | return _ForgotPasswordPageState(); 9 | } 10 | } 11 | 12 | class _ForgotPasswordPageState extends State { 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | body: CircularProgressIndicator(), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/ui/pages/forgot_password/forgot_password_state.dart: -------------------------------------------------------------------------------- 1 | // part of 'forgot_password_cubit.dart'; 2 | // 3 | // abstract class ForgotPasswordState extends Equatable { 4 | // const ForgotPasswordState(); 5 | // } 6 | // 7 | // class ForgotPasswordInitial extends ForgotPasswordState { 8 | // @override 9 | // List get props => []; 10 | // } 11 | -------------------------------------------------------------------------------- /lib/ui/pages/main/main_logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'main_state.dart'; 4 | 5 | class MainLogic extends GetxController { 6 | final state = MainState(); 7 | 8 | ///Switch tab 9 | void switchTap(int index) { 10 | state.selectedIndex.value = index; 11 | state.pageController.jumpToPage(index); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/ui/pages/main/main_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/ui/pages/tab_home/home_tab_view.dart'; 3 | import 'package:flutter_app/ui/pages/tab_profile/profile_tab_view.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | class MainState { 7 | ///Select index- responsive 8 | late RxInt selectedIndex; 9 | 10 | ///PageView page 11 | late List pageList; 12 | late PageController pageController; 13 | 14 | MainState() { 15 | //Initialize index 16 | selectedIndex = 0.obs; 17 | //PageView page 18 | pageList = [ 19 | HomeTabPage(), 20 | Container(color: Colors.green), 21 | Container(color: Colors.red), 22 | Container(color: Colors.green), 23 | ProfileTabPage(), 24 | ]; 25 | //Page controller 26 | pageController = PageController(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/ui/pages/main/main_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_colors.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import 'main_logic.dart'; 6 | import 'main_state.dart'; 7 | import 'tab/main_tab.dart'; 8 | 9 | class MainPage extends StatefulWidget { 10 | @override 11 | _MainPageState createState() => _MainPageState(); 12 | } 13 | 14 | class _MainPageState extends State { 15 | final MainLogic logic = Get.put(MainLogic()); 16 | final MainState state = Get.find().state; 17 | 18 | final tabs = [ 19 | MainTab.home, 20 | MainTab.discover, 21 | MainTab.tvShows, 22 | MainTab.watchlist, 23 | MainTab.profile, 24 | ]; 25 | 26 | @override 27 | void initState() { 28 | // TODO: implement initState 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Scaffold( 35 | body: _buildPageView(), 36 | bottomNavigationBar: _buildBottomNavigationBar(), 37 | ); 38 | } 39 | 40 | Widget _buildPageView() { 41 | return PageView( 42 | controller: state.pageController, 43 | children: state.pageList, 44 | onPageChanged: (index) { 45 | logic.switchTap(index); 46 | }, 47 | ); 48 | } 49 | 50 | Widget _buildBottomNavigationBar() { 51 | final theme = Theme.of(context); 52 | return Obx(() { 53 | return BottomNavigationBar( 54 | showSelectedLabels: false, 55 | showUnselectedLabels: false, 56 | backgroundColor: theme.appBarTheme.backgroundColor, 57 | elevation: 8, 58 | type: BottomNavigationBarType.fixed, 59 | currentIndex: state.selectedIndex.value, 60 | unselectedItemColor: Colors.grey, 61 | selectedItemColor: theme.accentColor, 62 | items: tabs.map((e) => e.tab).toList(), 63 | onTap: (index) { 64 | logic.switchTap(index); 65 | }, 66 | ); 67 | }); 68 | } 69 | 70 | @override 71 | void dispose() { 72 | Get.delete(); 73 | super.dispose(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/ui/pages/main/tab/main_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | enum MainTab { 4 | home, 5 | discover, 6 | tvShows, 7 | watchlist, 8 | profile, 9 | } 10 | 11 | extension MainTabExtension on MainTab { 12 | Widget get page { 13 | switch (this) { 14 | case MainTab.home: 15 | // return HomeTabPage(); 16 | // return Container(color: Colors.red,); 17 | case MainTab.discover: 18 | // return DiscoverTabPage(); 19 | case MainTab.tvShows: 20 | // return TvShowTabPage(); 21 | case MainTab.watchlist: 22 | // return WatchlistTabPage(); 23 | case MainTab.profile: 24 | // return ProfileTabPage(); 25 | } 26 | return Container(); 27 | } 28 | 29 | BottomNavigationBarItem get tab { 30 | switch (this) { 31 | case MainTab.home: 32 | return BottomNavigationBarItem(icon: Icon(Icons.home_rounded), label: 'Home'); 33 | case MainTab.discover: 34 | return BottomNavigationBarItem(icon: Icon(Icons.explore_outlined), label: 'Discover'); 35 | case MainTab.tvShows: 36 | return BottomNavigationBarItem(icon: Icon(Icons.tv_rounded), label: 'TV Shows'); 37 | case MainTab.watchlist: 38 | return BottomNavigationBarItem(icon: Icon(Icons.bookmark_outline_rounded), label: 'Watchlist'); 39 | case MainTab.profile: 40 | return BottomNavigationBarItem(icon: Icon(Icons.person_outline_rounded), label: 'Profile'); 41 | } 42 | } 43 | 44 | String get title { 45 | switch (this) { 46 | case MainTab.home: 47 | return 'Home'; 48 | case MainTab.discover: 49 | return 'Notification'; 50 | case MainTab.tvShows: 51 | return 'TV Shows'; 52 | case MainTab.watchlist: 53 | return 'Setting'; 54 | case MainTab.profile: 55 | return 'Profile'; 56 | } 57 | return ''; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/ui/pages/movie_detail/movie_detail_logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'movie_detail_state.dart'; 4 | 5 | class MovieDetailLogic extends GetxController { 6 | final state = MovieDetailState(); 7 | } 8 | -------------------------------------------------------------------------------- /lib/ui/pages/movie_detail/movie_detail_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class MovieDetailState { 4 | final temp1 = 0.obs; 5 | final temp2 = 0.obs; 6 | 7 | MovieDetailState() { 8 | ///Initialize variables 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/ui/pages/movie_detail/movie_detail_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import 'movie_detail_logic.dart'; 5 | import 'movie_detail_state.dart'; 6 | 7 | class MovieDetailPage extends StatefulWidget { 8 | @override 9 | _MovieDetailPageState createState() => _MovieDetailPageState(); 10 | } 11 | 12 | class _MovieDetailPageState extends State { 13 | final MovieDetailLogic logic = Get.put(MovieDetailLogic()); 14 | final MovieDetailState state = Get 15 | .find() 16 | .state; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | body: Container( 22 | child: Column( 23 | children: [ 24 | SizedBox(height: 100), 25 | tem1(), 26 | tem2(), 27 | Text("${state.temp2.value}"), 28 | TextButton( 29 | onPressed: () { 30 | state.temp1.value += 1; 31 | }, 32 | child: Text("aaaa"), 33 | ), 34 | TextButton( 35 | onPressed: () { 36 | state.temp2.value += 1; 37 | }, 38 | child: Text("bbbb"), 39 | ), 40 | ], 41 | ), 42 | ), 43 | ); 44 | } 45 | 46 | Widget tem1() { 47 | return Obx(() { 48 | return Text("${state.temp1.value}"); 49 | }); 50 | } 51 | 52 | Widget tem2() { 53 | return Obx(() { 54 | return Text("${state.temp2.value}"); 55 | }); 56 | } 57 | 58 | @override 59 | void dispose() { 60 | Get.delete(); 61 | super.dispose(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/ui/pages/setting/setting_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_dimens.dart'; 3 | import 'package:flutter_app/generated/l10n.dart'; 4 | import 'package:flutter_app/ui/widgets/appbar/app_bar_widget.dart'; 5 | import 'package:get/get.dart'; 6 | 7 | import '../../../services/setting_service.dart'; 8 | 9 | class SettingPage extends StatefulWidget { 10 | const SettingPage({Key? key}) : super(key: key); 11 | 12 | @override 13 | _SettingPageState createState() => _SettingPageState(); 14 | } 15 | 16 | class _SettingPageState extends State { 17 | final settingService = Get.find(); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | appBar: AppBarWidget( 23 | title: S.of(context).settings_title, 24 | onBackPressed: () { 25 | Get.back(); 26 | }, 27 | ), 28 | body: Container( 29 | padding: EdgeInsets.all(AppDimens.paddingNormal), 30 | child: Column( 31 | mainAxisAlignment: MainAxisAlignment.start, 32 | crossAxisAlignment: CrossAxisAlignment.start, 33 | children: [ 34 | _buildThemeSection(), 35 | _buildLanguageSection(), 36 | ], 37 | ), 38 | ), 39 | ); 40 | } 41 | 42 | Widget _buildThemeSection() { 43 | final theme = Theme.of(context); 44 | return Obx(() { 45 | return Column( 46 | mainAxisAlignment: MainAxisAlignment.start, 47 | crossAxisAlignment: CrossAxisAlignment.start, 48 | children: [ 49 | Text( 50 | S.of(context).settings_themeMode, 51 | style: theme.textTheme.headline6, 52 | ), 53 | RadioListTile( 54 | title: Text(S.of(context).settings_themeModeSystem), 55 | value: ThemeMode.system, 56 | groupValue: settingService.currentThemeMode.value, 57 | onChanged: (ThemeMode? value) { 58 | if (value != null) { 59 | settingService.changeThemeMode(value); 60 | } 61 | }, 62 | ), 63 | RadioListTile( 64 | title: Text(S.of(context).settings_themeModeLight), 65 | value: ThemeMode.light, 66 | groupValue: settingService.currentThemeMode.value, 67 | onChanged: (ThemeMode? value) { 68 | if (value != null) { 69 | settingService.changeThemeMode(value); 70 | } 71 | }, 72 | ), 73 | RadioListTile( 74 | title: Text(S.of(context).settings_themeModeDark), 75 | value: ThemeMode.dark, 76 | groupValue: settingService.currentThemeMode.value, 77 | onChanged: (ThemeMode? value) { 78 | if (value != null) { 79 | settingService.changeThemeMode(value); 80 | } 81 | }, 82 | ), 83 | ], 84 | ); 85 | }); 86 | ; 87 | } 88 | 89 | Widget _buildLanguageSection() { 90 | final theme = Theme.of(context); 91 | return Obx(() { 92 | return Column( 93 | mainAxisAlignment: MainAxisAlignment.start, 94 | crossAxisAlignment: CrossAxisAlignment.start, 95 | children: [ 96 | Text( 97 | S.of(context).settings_language, 98 | style: theme.textTheme.headline6, 99 | ), 100 | RadioListTile( 101 | title: Text(S.of(context).settings_languageEnglish), 102 | value: Locale.fromSubtags(languageCode: 'en'), 103 | groupValue: settingService.currentLocate.value, 104 | onChanged: (Locale? value) { 105 | if (value != null) { 106 | settingService.updateLocale(value); 107 | } 108 | }, 109 | ), 110 | RadioListTile( 111 | title: Text(S.of(context).settings_languageVietnamese), 112 | value: Locale.fromSubtags(languageCode: 'vi'), 113 | groupValue: settingService.currentLocate.value, 114 | onChanged: (Locale? value) { 115 | if (value != null) { 116 | settingService.updateLocale(value); 117 | } 118 | }, 119 | ), 120 | ], 121 | ); 122 | }); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/ui/pages/sign_in/sign_in_logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_app/model/entities/index.dart'; 2 | import 'package:flutter_app/model/enums/load_status.dart'; 3 | import 'package:flutter_app/repositories/auth_repository.dart'; 4 | import 'package:flutter_app/router/route_config.dart'; 5 | import 'package:flutter_app/ui/commons/app_snackbar.dart'; 6 | import 'package:flutter_app/utils/logger.dart'; 7 | import 'package:get/get.dart'; 8 | 9 | import 'sign_in_state.dart'; 10 | 11 | class SignInLogic extends GetxController { 12 | final state = SignInState(); 13 | final _authRepository = Get.find(tag: (AuthRepository).toString()); 14 | 15 | void signIn() async { 16 | final username = state.usernameTextController.text; 17 | final password = state.passwordTextController.text; 18 | if (username.isEmpty) { 19 | AppSnackbar.showError(message: 'Username is empty'); 20 | return; 21 | } 22 | if (password.isEmpty) { 23 | AppSnackbar.showError(message: 'Password is empty'); 24 | return; 25 | } 26 | state.signInStatus.value = LoadStatus.loading; 27 | try { 28 | final result = await _authRepository.signIn(username, password); 29 | if (result != null) { 30 | UserEntity? myProfile = await _authRepository.getProfile(); 31 | 32 | _authRepository.saveToken(result); 33 | state.signInStatus.value = LoadStatus.success; 34 | Get.offNamed(RouteConfig.main); 35 | } else { 36 | state.signInStatus.value = LoadStatus.failure; 37 | } 38 | } catch (error) { 39 | logger.e(error); 40 | state.signInStatus.value = LoadStatus.failure; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/ui/pages/sign_in/sign_in_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/model/enums/load_status.dart'; 3 | import 'package:flutter_app/ui/widgets/input/app_password_input.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | class SignInState { 7 | late TextEditingController usernameTextController; 8 | late TextEditingController passwordTextController; 9 | 10 | late ObscureTextController obscurePasswordController; 11 | 12 | final signInStatus = LoadStatus.initial.obs; 13 | 14 | SignInState() { 15 | usernameTextController = TextEditingController(text: 'thoson.it@gmail.com'); 16 | passwordTextController = TextEditingController(text: "Son@1234"); 17 | obscurePasswordController = ObscureTextController(obscureText: true); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/ui/pages/sign_in/sign_in_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_colors.dart'; 3 | import 'package:flutter_app/common/app_images.dart'; 4 | import 'package:flutter_app/common/app_text_styles.dart'; 5 | import 'package:flutter_app/model/enums/load_status.dart'; 6 | import 'package:flutter_app/ui/widgets/buttons/app_tint_button.dart'; 7 | import 'package:flutter_app/ui/widgets/input/app_email_input.dart'; 8 | import 'package:flutter_app/ui/widgets/input/app_password_input.dart'; 9 | import 'package:get/get.dart'; 10 | 11 | import 'sign_in_logic.dart'; 12 | import 'sign_in_state.dart'; 13 | 14 | class SignInPage extends StatefulWidget { 15 | @override 16 | _SignInPageState createState() => _SignInPageState(); 17 | } 18 | 19 | class _SignInPageState extends State { 20 | final SignInLogic logic = Get.put(SignInLogic()); 21 | final SignInState state = Get.find().state; 22 | 23 | @override 24 | void dispose() { 25 | Get.delete(); 26 | super.dispose(); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Scaffold( 32 | backgroundColor: AppColors.background, 33 | body: buildBodyWidget(), 34 | resizeToAvoidBottomInset: false, 35 | ); 36 | } 37 | 38 | Widget buildBodyWidget() { 39 | final showingKeyboard = MediaQuery.of(context).viewInsets.bottom != 0; 40 | return Column( 41 | mainAxisAlignment: MainAxisAlignment.start, 42 | children: [ 43 | SizedBox(height: 100), 44 | Container(height: showingKeyboard ? 0 : 200, width: 200, child: Image.asset(AppImages.icLogoTransparent)), 45 | Container( 46 | margin: EdgeInsets.symmetric(horizontal: 20), 47 | child: AppEmailInput( 48 | textEditingController: state.usernameTextController, 49 | labelStyle: AppTextStyle.whiteS14Bold, 50 | textStyle: AppTextStyle.whiteS14, 51 | ), 52 | ), 53 | SizedBox(height: 12), 54 | Container( 55 | margin: EdgeInsets.symmetric(horizontal: 20), 56 | child: AppPasswordInput( 57 | obscureTextController: state.obscurePasswordController, 58 | textEditingController: state.passwordTextController, 59 | labelStyle: AppTextStyle.whiteS14Bold, 60 | textStyle: AppTextStyle.whiteS14, 61 | onChanged: (password) { 62 | //Todo 63 | }, 64 | ), 65 | ), 66 | SizedBox(height: 32), 67 | _buildSignButton(), 68 | ], 69 | ); 70 | } 71 | 72 | Widget _buildSignButton() { 73 | return Obx(() { 74 | return Container( 75 | padding: EdgeInsets.symmetric(horizontal: 20), 76 | child: AppTintButton( 77 | title: 'Sign In', 78 | onPressed: _signIn, 79 | isLoading: state.signInStatus.value == LoadStatus.loading, 80 | ), 81 | ); 82 | }); 83 | } 84 | 85 | void _signIn() { 86 | logic.signIn(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/ui/pages/sign_up/sign_up_cubit.dart: -------------------------------------------------------------------------------- 1 | // import 'package:bloc/bloc.dart'; 2 | // import 'package:equatable/equatable.dart'; 3 | // 4 | // part 'sign_up_state.dart'; 5 | // 6 | // class SignUpCubit extends Cubit { 7 | // SignUpCubit() : super(SignUpInitial()); 8 | // } 9 | -------------------------------------------------------------------------------- /lib/ui/pages/sign_up/sign_up_page.dart: -------------------------------------------------------------------------------- 1 | // import 'package:flutter/cupertino.dart'; 2 | // import 'package:flutter/material.dart'; 3 | // import 'package:flutter_app/common/app_colors.dart'; 4 | // 5 | // class SignUpPage extends StatefulWidget { 6 | // @override 7 | // State createState() { 8 | // return _SignUpPageState(); 9 | // } 10 | // } 11 | // 12 | // class _SignUpPageState extends State { 13 | // @override 14 | // Widget build(BuildContext context) { 15 | // return Scaffold( 16 | // backgroundColor: AppColors.main, 17 | // body: Text('SignUp Page'), 18 | // ); 19 | // } 20 | // } 21 | -------------------------------------------------------------------------------- /lib/ui/pages/sign_up/sign_up_state.dart: -------------------------------------------------------------------------------- 1 | // part of 'sign_up_cubit.dart'; 2 | // 3 | // abstract class SignUpState extends Equatable { 4 | // const SignUpState(); 5 | // } 6 | // 7 | // class SignUpInitial extends SignUpState { 8 | // @override 9 | // List get props => []; 10 | // } 11 | -------------------------------------------------------------------------------- /lib/ui/pages/splash/splash_logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_app/model/entities/index.dart'; 3 | import 'package:flutter_app/repositories/auth_repository.dart'; 4 | import 'package:flutter_app/ui/commons/app_dialog.dart'; 5 | import 'package:flutter_app/ui/pages/main/main_view.dart'; 6 | import 'package:flutter_app/ui/pages/sign_in/sign_in_view.dart'; 7 | import 'package:flutter_app/utils/logger.dart'; 8 | import 'package:get/get.dart'; 9 | 10 | import 'splash_state.dart'; 11 | 12 | class SplashLogic extends GetxController { 13 | final state = SplashState(); 14 | final _authRepository = Get.find(tag: (AuthRepository).toString()); 15 | 16 | void checkLogin() async { 17 | await Future.delayed(Duration(seconds: 2)); 18 | final isLoggedIn = await _authRepository.isLoggedIn(); 19 | if (!isLoggedIn) { 20 | Get.offAll(SignInPage()); 21 | } else { 22 | try { 23 | //Profile 24 | UserEntity? myProfile = await _authRepository.getProfile(); 25 | //Todo 26 | // _authRepository.updateUser(myProfile); 27 | } catch (error, s) { 28 | logger.e(error, s); 29 | //Check 401 30 | if (error is DioError) { 31 | if (error.response?.statusCode == 401) { 32 | _authRepository.signOut(); 33 | checkLogin(); 34 | return; 35 | } 36 | } 37 | AppDialog.defaultDialog( 38 | message: "An error happened. Please check your connection!", 39 | textConfirm: "Retry", 40 | onConfirm: () { 41 | checkLogin(); 42 | }, 43 | ); 44 | return; 45 | } 46 | Get.offAll(MainPage()); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/ui/pages/splash/splash_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_app/model/enums/load_status.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | class SplashState { 5 | Rx loginState = LoadStatus.initial.obs; 6 | 7 | SplashState() { 8 | ///Initialize variables 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/ui/pages/splash/splash_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_images.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | import 'splash_logic.dart'; 6 | import 'splash_state.dart'; 7 | 8 | class SplashPage extends StatefulWidget { 9 | @override 10 | _SplashPageState createState() => _SplashPageState(); 11 | } 12 | 13 | class _SplashPageState extends State { 14 | final SplashLogic logic = Get.put(SplashLogic()); 15 | final SplashState state = Get.find().state; 16 | 17 | @override 18 | void initState() { 19 | // TODO: implement initState 20 | super.initState(); 21 | logic.checkLogin(); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return Scaffold( 27 | body: Stack( 28 | children: [ 29 | Center( 30 | child: Container( 31 | height: 200, 32 | width: 200, 33 | child: Image.asset(AppImages.icLogoTransparent), 34 | ), 35 | ), 36 | ], 37 | ), 38 | ); 39 | } 40 | 41 | @override 42 | void dispose() { 43 | Get.delete(); 44 | super.dispose(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_home/enums/home_section.dart: -------------------------------------------------------------------------------- 1 | enum HomeSection { 2 | trendingMovies, 3 | trendingTvShows, 4 | nowPlayingMovies, 5 | upcomingMovies, 6 | } 7 | 8 | extension HomeSectionExtension on HomeSection { 9 | String get title { 10 | switch (this) { 11 | case HomeSection.trendingMovies: 12 | return "Trending"; 13 | case HomeSection.trendingTvShows: 14 | return "Trending"; 15 | case HomeSection.nowPlayingMovies: 16 | return "Now playing"; 17 | case HomeSection.upcomingMovies: 18 | return "Upcoming"; 19 | default: 20 | return ""; 21 | } 22 | } 23 | 24 | String get typeName { 25 | switch (this) { 26 | case HomeSection.trendingMovies: 27 | return "Movies"; 28 | case HomeSection.trendingTvShows: 29 | return "Tv Shows"; 30 | case HomeSection.nowPlayingMovies: 31 | return "Movies"; 32 | case HomeSection.upcomingMovies: 33 | return "Movies"; 34 | default: 35 | return ""; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_home/home_tab_logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'home_tab_state.dart'; 4 | 5 | class HomeTabLogic extends GetxController { 6 | final state = HomeTabState(); 7 | } 8 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_home/home_tab_state.dart: -------------------------------------------------------------------------------- 1 | class HomeTabState { 2 | HomeTabState() { 3 | ///Initialize variables 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_home/home_tab_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/services/app_service.dart'; 3 | import 'package:flutter_app/ui/pages/tab_home/enums/home_section.dart'; 4 | import 'package:flutter_app/ui/pages/tab_home/movies_section/movies_section_view.dart'; 5 | import 'package:flutter_app/ui/pages/tab_home/widgets/home_app_bar.dart'; 6 | import 'package:get/get.dart'; 7 | 8 | import 'home_tab_logic.dart'; 9 | import 'home_tab_state.dart'; 10 | 11 | class HomeTabPage extends StatefulWidget { 12 | @override 13 | _HomeTabPageState createState() => _HomeTabPageState(); 14 | } 15 | 16 | class _HomeTabPageState extends State 17 | with AutomaticKeepAliveClientMixin { 18 | final HomeTabLogic logic = Get.put(HomeTabLogic()); 19 | final AppService _appService = Get.put(AppService()); 20 | final HomeTabState state = Get.find().state; 21 | 22 | @override 23 | bool get wantKeepAlive => true; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | } 29 | 30 | @override 31 | void dispose() { 32 | super.dispose(); 33 | Get.delete(); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | super.build(context); 39 | return Obx(() { 40 | return Scaffold( 41 | appBar: HomeAppBar( 42 | avatarUrl: _appService.user.value?.avatarUrl ?? "", 43 | ), 44 | body: SafeArea( 45 | child: RefreshIndicator( 46 | onRefresh: _onRefreshData, 47 | child: SingleChildScrollView( 48 | child: Column( 49 | children: [ 50 | _buildTrendingMovies(), 51 | _buildTrendingTvShows(), 52 | _buildNowPlayingMovies(), 53 | _buildUpcomingMovies(), 54 | ], 55 | ), 56 | ), 57 | ), 58 | ), 59 | ); 60 | }); 61 | } 62 | 63 | Widget _buildTrendingMovies() { 64 | return MoviesSectionPage(HomeSection.trendingMovies); 65 | } 66 | 67 | Widget _buildTrendingTvShows() { 68 | return MoviesSectionPage(HomeSection.trendingTvShows); 69 | } 70 | 71 | Widget _buildNowPlayingMovies() { 72 | return MoviesSectionPage(HomeSection.nowPlayingMovies); 73 | } 74 | 75 | Widget _buildUpcomingMovies() { 76 | return MoviesSectionPage(HomeSection.upcomingMovies); 77 | } 78 | 79 | Future _onRefreshData() async { 80 | // _trendingMoviesCubit.fetchInitialMovies(); 81 | // _trendingTvShowsCubit.fetchInitialMovies(); 82 | // _nowPlayingMoviesCubit.fetchInitialMovies(); 83 | // _upcomingMoviesCubit.fetchInitialMovies(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_home/movies_section/movies_section_logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_app/model/enums/load_status.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../../repositories/movie_repository.dart'; 5 | import 'movies_section_state.dart'; 6 | 7 | class MoviesSectionLogic extends GetxController { 8 | final state = MoviesSectionState(); 9 | final _movieRepository = Get.find(); 10 | 11 | void fetchInitialMovies() async { 12 | state.loadMovieStatus.value = LoadStatus.loading; 13 | try { 14 | final result = await _movieRepository.getMovies(page: 1); 15 | state.loadMovieStatus.value = LoadStatus.success; 16 | state.movies.value = result.results; 17 | state.page.value = result.page; 18 | state.totalPages.value = result.totalPages; 19 | } catch (e) { 20 | state.loadMovieStatus.value = LoadStatus.failure; 21 | } 22 | } 23 | 24 | void fetchNextMovies() async { 25 | if (state.page.value == state.totalPages.value) { 26 | return; 27 | } 28 | if (state.loadMovieStatus.value != LoadStatus.success) { 29 | return; 30 | } 31 | state.loadMovieStatus.value = LoadStatus.loadingMore; 32 | try { 33 | final result = 34 | await _movieRepository.getMovies(page: state.page.value + 1); 35 | state.loadMovieStatus.value = LoadStatus.success; 36 | state.movies.value = result.results; 37 | state.page.value = state.page.value + result.page; 38 | state.page.value = result.page; 39 | state.totalPages.value = result.totalPages; 40 | } catch (e) { 41 | state.loadMovieStatus.value = LoadStatus.success; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_home/movies_section/movies_section_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_app/model/entities/movie_entity.dart'; 2 | import 'package:flutter_app/model/enums/load_status.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | class MoviesSectionState { 6 | final loadMovieStatus = LoadStatus.initial.obs; 7 | final movies = [].obs; 8 | final page = 1.obs; 9 | final totalResults = 0.obs; 10 | final totalPages = 0.obs; 11 | 12 | MoviesSectionState() { 13 | ///Initialize variables 14 | } 15 | 16 | bool get hasReachedMax { 17 | return page >= totalPages.value; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_home/movies_section/movies_section_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_text_styles.dart'; 3 | import 'package:flutter_app/model/entities/index.dart'; 4 | import 'package:flutter_app/model/enums/load_status.dart'; 5 | import 'package:flutter_app/ui/pages/movie_detail/movie_detail_view.dart'; 6 | import 'package:flutter_app/ui/pages/tab_home/enums/home_section.dart'; 7 | import 'package:flutter_app/ui/pages/tab_home/movies_section/widgets/loading_list_widget.dart'; 8 | import 'package:flutter_app/ui/pages/tab_home/movies_section/widgets/movie_widget.dart'; 9 | import 'package:flutter_app/ui/widgets/loading_more_row_widget.dart'; 10 | import 'package:get/get.dart'; 11 | 12 | import 'movies_section_logic.dart'; 13 | import 'movies_section_state.dart'; 14 | 15 | class MoviesSectionPage extends StatefulWidget { 16 | final HomeSection section; 17 | 18 | MoviesSectionPage(this.section); 19 | 20 | @override 21 | _MoviesSectionPageState createState() => _MoviesSectionPageState(); 22 | } 23 | 24 | class _MoviesSectionPageState extends State { 25 | final MoviesSectionLogic logic = Get.put(MoviesSectionLogic()); 26 | final MoviesSectionState state = Get.find().state; 27 | 28 | final _scrollController = ScrollController(); 29 | final _scrollThreshold = 200.0; 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | _scrollController.addListener(_onScroll); 35 | logic.fetchInitialMovies(); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | final theme = Theme.of(context); 41 | return Container( 42 | child: Column( 43 | children: [ 44 | Container( 45 | padding: EdgeInsets.all(20), 46 | child: Row( 47 | crossAxisAlignment: CrossAxisAlignment.end, 48 | children: [ 49 | Text( 50 | "${widget.section.title}", 51 | style: theme.textTheme.headline6, 52 | ), 53 | SizedBox(width: 10), 54 | Text( 55 | "${widget.section.typeName}", 56 | style: theme.textTheme.subtitle2, 57 | ) 58 | ], 59 | ), 60 | ), 61 | Container( 62 | height: 160, 63 | width: double.infinity, 64 | child: buildContentWidget(), 65 | ) 66 | ], 67 | ), 68 | ); 69 | } 70 | 71 | Widget buildContentWidget() { 72 | return Obx(() { 73 | if (state.loadMovieStatus.value == LoadStatus.loading) { 74 | return _buildLoadingList(); 75 | } else if (state.loadMovieStatus.value == LoadStatus.failure) { 76 | return Container(); 77 | } else { 78 | return _buildSuccessList( 79 | state.movies, 80 | showLoadingMore: !state.hasReachedMax, 81 | ); 82 | } 83 | }); 84 | } 85 | 86 | Widget _buildLoadingList() { 87 | return LoadingListWidget(); 88 | } 89 | 90 | Widget _buildSuccessList(List items, {bool showLoadingMore = false}) { 91 | return Container( 92 | child: ListView.builder( 93 | controller: _scrollController, 94 | padding: EdgeInsets.symmetric(horizontal: 15), 95 | scrollDirection: Axis.horizontal, 96 | itemBuilder: (context, index) { 97 | if (index < items.length) { 98 | final item = items[index]; 99 | return Container( 100 | height: 160, 101 | width: 82, 102 | margin: EdgeInsets.symmetric(horizontal: 5), 103 | child: MovieWidget( 104 | movie: item, 105 | onPressed: () { 106 | Get.to(MovieDetailPage()); 107 | }, 108 | ), 109 | ); 110 | } else { 111 | return LoadingMoreRowWidget(); 112 | } 113 | }, 114 | itemCount: showLoadingMore ? items.length + 1 : items.length, 115 | // controller: _scrollController, 116 | ), 117 | ); 118 | } 119 | 120 | void _onScroll() { 121 | final maxScroll = _scrollController.position.maxScrollExtent; 122 | final currentScroll = _scrollController.position.pixels; 123 | if (maxScroll - currentScroll <= _scrollThreshold) { 124 | logic.fetchNextMovies(); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_home/movies_section/widgets/loading_list_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../widgets/shimmer/app_shimmer.dart'; 4 | 5 | class LoadingListWidget extends StatelessWidget { 6 | final double rowHeight; 7 | 8 | LoadingListWidget({this.rowHeight = 100}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | child: ListView.builder( 14 | scrollDirection: Axis.horizontal, 15 | padding: EdgeInsets.symmetric(horizontal: 15), 16 | itemBuilder: (context, index) { 17 | return Container( 18 | height: 160, 19 | width: 82, 20 | margin: EdgeInsets.symmetric(horizontal: 5), 21 | child: Column( 22 | children: [ 23 | AppShimmer( 24 | height: 122, 25 | width: 82, 26 | cornerRadius: 8, 27 | ), 28 | SizedBox(height: 10), 29 | AppShimmer( 30 | height: 10, 31 | width: 82, 32 | cornerRadius: 8, 33 | ), 34 | SizedBox(height: 4), 35 | AppShimmer( 36 | height: 10, 37 | width: 82, 38 | cornerRadius: 8, 39 | ), 40 | SizedBox(height: 4), 41 | ], 42 | ), 43 | ); 44 | }, 45 | itemCount: 20, 46 | physics: NeverScrollableScrollPhysics(), 47 | ), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_home/movies_section/widgets/movie_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_colors.dart'; 3 | import 'package:flutter_app/model/entities/movie_entity.dart'; 4 | import 'package:flutter_app/ui/widgets/images/app_cache_image.dart'; 5 | 6 | class MovieWidget extends StatelessWidget { 7 | final MovieEntity? movie; 8 | final VoidCallback? onPressed; 9 | 10 | MovieWidget({this.movie, this.onPressed}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final theme = Theme.of(context); 15 | return Container( 16 | child: FlatButton( 17 | padding: EdgeInsets.all(0), 18 | child: Container( 19 | child: Column( 20 | children: [ 21 | Expanded( 22 | child: Container( 23 | child: _buildThumbWidget(), 24 | ), 25 | ), 26 | Row( 27 | children: [ 28 | Expanded( 29 | child: Container( 30 | child: Text(movie?.title ?? '', style: theme.textTheme.caption, maxLines: 2), 31 | height: 32, 32 | margin: EdgeInsets.only(top: 5), 33 | ), 34 | ), 35 | GestureDetector( 36 | child: Container( 37 | child: Icon(Icons.more_vert, color: AppColors.secondary, size: 16), 38 | height: 32, 39 | ), 40 | ) 41 | ], 42 | ) 43 | ], 44 | ), 45 | ), 46 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), 47 | onPressed: onPressed, 48 | ), 49 | ); 50 | } 51 | 52 | Widget _buildThumbWidget() { 53 | return Container( 54 | decoration: BoxDecoration( 55 | borderRadius: BorderRadius.all(Radius.circular(4)), 56 | color: AppColors.imageBG, 57 | ), 58 | child: ClipRRect( 59 | borderRadius: BorderRadius.all(Radius.circular(4)), 60 | child: AppCacheImage( 61 | url: movie?.posterUrl ?? '', 62 | fit: BoxFit.cover, 63 | ), 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_home/widgets/home_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_app/common/app_dimens.dart'; 4 | import 'package:flutter_app/ui/widgets/images/app_cache_image.dart'; 5 | 6 | class HomeAppBar extends AppBar { 7 | HomeAppBar({ 8 | Key? key, 9 | String avatarUrl = "", 10 | VoidCallback? onSearchPressed, 11 | VoidCallback? onSettingPressed, 12 | }) : super( 13 | key: key, 14 | title: Text("Movie"), 15 | leading: Container( 16 | child: Center( 17 | child: AppCircleAvatar( 18 | url: avatarUrl, 19 | size: 40, 20 | ), 21 | )), 22 | toolbarHeight: AppDimens.appBarHeight, 23 | actions: [ 24 | IconButton( 25 | onPressed: null, 26 | icon: Icon( 27 | Icons.search, 28 | )), 29 | IconButton( 30 | onPressed: null, 31 | icon: Icon( 32 | Icons.more_vert, 33 | )), 34 | ], 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_profile/profile_tab_logic.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_app/model/enums/load_status.dart'; 2 | import 'package:flutter_app/repositories/auth_repository.dart'; 3 | import 'package:flutter_app/ui/pages/sign_in/sign_in_view.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | import 'profile_tab_state.dart'; 7 | 8 | class ProfileTabLogic extends GetxController { 9 | final state = ProfileTabState(); 10 | 11 | final _authRepository = Get.find(tag: (AuthRepository).toString()); 12 | 13 | void signOut() async { 14 | state.signOutStatus.value = LoadStatus.loading; 15 | 16 | ///Call signOut API here 17 | await Future.delayed(Duration(seconds: 2)); 18 | _authRepository.signOut(); 19 | state.signOutStatus.value = LoadStatus.success; 20 | Get.off(SignInPage()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_profile/profile_tab_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_app/model/entities/index.dart'; 2 | import 'package:flutter_app/model/enums/load_status.dart'; 3 | import 'package:get/get.dart'; 4 | 5 | class ProfileTabState { 6 | Rxn user = Rxn(); 7 | 8 | final signOutStatus = LoadStatus.initial.obs; 9 | 10 | ProfileTabState() {} 11 | } 12 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_profile/profile_tab_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/model/enums/load_status.dart'; 3 | import 'package:flutter_app/ui/pages/setting/setting_page.dart'; 4 | import 'package:flutter_app/ui/widgets/buttons/app_white_button.dart'; 5 | import 'package:flutter_app/ui/widgets/images/app_cache_image.dart'; 6 | import 'package:get/get.dart'; 7 | 8 | import 'profile_tab_logic.dart'; 9 | import 'profile_tab_state.dart'; 10 | import 'widgets/menu_header_widget.dart'; 11 | import 'widgets/menu_item_widget.dart'; 12 | 13 | class ProfileTabPage extends StatefulWidget { 14 | @override 15 | _ProfileTabPageState createState() => _ProfileTabPageState(); 16 | } 17 | 18 | class _ProfileTabPageState extends State with AutomaticKeepAliveClientMixin { 19 | final ProfileTabLogic logic = Get.put(ProfileTabLogic()); 20 | final ProfileTabState state = Get.find().state; 21 | 22 | @override 23 | void dispose() { 24 | Get.delete(); 25 | super.dispose(); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | super.build(context); 31 | return Scaffold( 32 | appBar: buildAppBar(), 33 | body: ListView( 34 | children: [ 35 | buildMenusWidget(), 36 | SizedBox(height: 10), 37 | buildSignOutButton(), 38 | SizedBox(height: 10), 39 | ], 40 | ), 41 | ); 42 | } 43 | 44 | AppBar buildAppBar() { 45 | final theme = Theme.of(context); 46 | return AppBar( 47 | // margin: EdgeInsets.all(20), 48 | // height: 60, 49 | // preferredSize: Size(double.infinity, 60), 50 | toolbarHeight: 56, 51 | leading: Container( 52 | padding: EdgeInsets.all(8), 53 | child: AppCircleAvatar(url: state.user.value?.avatarUrl ?? "", size: 48), 54 | ), 55 | title: Row( 56 | children: [ 57 | // AppCircleAvatar(url: state.user.value?.avatarUrl ?? "", size: 60), 58 | SizedBox(width: 20), 59 | Expanded( 60 | child: Column( 61 | crossAxisAlignment: CrossAxisAlignment.start, 62 | mainAxisAlignment: MainAxisAlignment.center, 63 | children: [ 64 | Text( 65 | "${state.user.value?.username ?? ""}", 66 | style: theme.textTheme.headline6, 67 | ), 68 | SizedBox(width: 10), 69 | Text( 70 | "View profile", 71 | style: theme.textTheme.subtitle2, 72 | ), 73 | ], 74 | ), 75 | ) 76 | ], 77 | ), 78 | ); 79 | } 80 | 81 | Widget buildMenusWidget() { 82 | return Column( 83 | children: [ 84 | MenuHeaderWidget(title: "Lists"), 85 | // MenuItemWidget(title: "Watchlist"), 86 | MenuItemWidget(title: "History"), 87 | // MenuItemWidget(title: "Collection"), 88 | // MenuItemWidget(title: "Personal Lists"), 89 | // MenuItemWidget(title: "Reminders"), 90 | // MenuItemWidget(title: "Hidden Items"), 91 | MenuHeaderWidget(title: "Settings"), 92 | // MenuItemWidget(title: "Go Premium"), 93 | MenuItemWidget( 94 | title: "Settings", 95 | onPressed: () { 96 | Get.to(() => SettingPage()); 97 | }, 98 | ), 99 | MenuItemWidget(title: "Help & feedback"), 100 | MenuItemWidget(title: "About"), 101 | ], 102 | ); 103 | } 104 | 105 | Widget buildSignOutButton() { 106 | return Obx(() { 107 | return Container( 108 | margin: EdgeInsets.symmetric(horizontal: 20), 109 | child: AppWhiteButton( 110 | title: 'Logout', 111 | isLoading: state.signOutStatus.value == LoadStatus.loading, 112 | onPressed: _handleSignOut, 113 | ), 114 | ); 115 | }); 116 | } 117 | 118 | void _handleSignOut() { 119 | logic.signOut(); 120 | } 121 | 122 | @override 123 | bool get wantKeepAlive => true; 124 | } 125 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_profile/widgets/menu_header_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_dimens.dart'; 3 | import 'package:flutter_app/common/app_text_styles.dart'; 4 | import 'package:flutter_app/ui/widgets/app_devider.dart'; 5 | 6 | class MenuHeaderWidget extends StatelessWidget { 7 | final String title; 8 | 9 | MenuHeaderWidget({this.title = "", Key? key}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final theme = Theme.of(context); 14 | return Container( 15 | padding: EdgeInsets.only(left: AppDimens.marginNormal, top: AppDimens.marginLarge), 16 | child: Column( 17 | mainAxisSize: MainAxisSize.min, 18 | children: [ 19 | Container( 20 | alignment: Alignment.centerLeft, 21 | child: Text( 22 | title, 23 | style: theme.textTheme.headline6, 24 | ), 25 | ), 26 | SizedBox(height: AppDimens.marginSmall), 27 | AppDivider(), 28 | ], 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/ui/pages/tab_profile/widgets/menu_item_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_colors.dart'; 3 | import 'package:flutter_app/common/app_dimens.dart'; 4 | import 'package:flutter_app/common/app_text_styles.dart'; 5 | import 'package:flutter_app/ui/widgets/app_devider.dart'; 6 | 7 | class MenuItemWidget extends StatelessWidget { 8 | final String title; 9 | final VoidCallback? onPressed; 10 | 11 | MenuItemWidget({Key? key, this.title = "", this.onPressed}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | final theme = Theme.of(context); 16 | return GestureDetector( 17 | onTap: onPressed, 18 | child: Container( 19 | padding: EdgeInsets.only( 20 | left: AppDimens.marginNormal, 21 | top: AppDimens.paddingSmall, 22 | ), 23 | child: Column( 24 | mainAxisSize: MainAxisSize.min, 25 | children: [ 26 | Container( 27 | alignment: Alignment.centerLeft, 28 | child: Row( 29 | children: [ 30 | Icon( 31 | Icons.bookmark_outline_rounded, 32 | ), 33 | SizedBox(width: 10), 34 | Expanded( 35 | child: Text( 36 | title, 37 | style: theme.textTheme.bodyText1, 38 | ), 39 | ), 40 | Icon( 41 | Icons.keyboard_arrow_right, 42 | ), 43 | SizedBox(width: AppDimens.paddingSmall), 44 | ], 45 | ), 46 | ), 47 | SizedBox(height: AppDimens.paddingSmall), 48 | AppDivider(), 49 | ], 50 | ), 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/ui/widgets/app_circular_progress_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_colors.dart'; 3 | 4 | class AppCircularProgressIndicator extends StatelessWidget { 5 | final Color color; 6 | 7 | AppCircularProgressIndicator({this.color = AppColors.secondary}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | alignment: Alignment.center, 13 | child: Container( 14 | width: 24, 15 | height: 24, 16 | child: CircularProgressIndicator( 17 | backgroundColor: color, 18 | valueColor: AlwaysStoppedAnimation(AppColors.secondary), 19 | ), 20 | ), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/ui/widgets/app_devider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppDivider extends Divider { 4 | final double? indent; 5 | final double? endIndent; 6 | 7 | AppDivider({this.indent = 0, this.endIndent = 0}) 8 | : super( 9 | color: Colors.grey, 10 | height: 1, 11 | indent: indent, 12 | endIndent: endIndent, 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /lib/ui/widgets/appbar/app_bar_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class AppBarWidget extends AppBar { 5 | AppBarWidget({ 6 | Key? key, 7 | VoidCallback? onBackPressed, 8 | String title = "", 9 | List rightActions = const [], 10 | }) : super( 11 | key: key, 12 | title: Text(title), 13 | toolbarHeight: 50, 14 | leading: IconButton(onPressed: onBackPressed, icon: Icon(Icons.arrow_back_ios_rounded)), 15 | actions: rightActions, 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /lib/ui/widgets/buttons/app_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_dimens.dart'; 3 | 4 | import '../app_circular_progress_indicator.dart'; 5 | 6 | class AppButton extends StatelessWidget { 7 | final String? title; 8 | final Widget? leadingIcon; 9 | final Widget? trailingIcon; 10 | 11 | final bool isLoading; 12 | 13 | final double? height; 14 | final double? width; 15 | final double? borderWidth; 16 | final double? cornerRadius; 17 | 18 | final Color? backgroundColor; 19 | final Color? borderColor; 20 | 21 | final TextStyle? textStyle; 22 | 23 | final VoidCallback? onPressed; 24 | 25 | AppButton({ 26 | this.title, 27 | this.leadingIcon, 28 | this.trailingIcon, 29 | this.isLoading = false, 30 | this.height, 31 | this.width, 32 | this.borderWidth, 33 | this.cornerRadius, 34 | this.backgroundColor, 35 | this.borderColor, 36 | this.textStyle, 37 | this.onPressed, 38 | }); 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return Container( 43 | height: height ?? AppDimens.buttonHeight, 44 | width: width ?? double.infinity, 45 | child: ElevatedButton( 46 | child: _buildChildWidget(), 47 | style: ElevatedButton.styleFrom( 48 | shape: RoundedRectangleBorder( 49 | borderRadius: BorderRadius.circular(cornerRadius ?? AppDimens.buttonCornerRadius), 50 | ), 51 | side: BorderSide( 52 | color: borderColor ?? Colors.transparent, 53 | width: borderWidth ?? 0, 54 | ), 55 | primary: backgroundColor, 56 | padding: EdgeInsets.all(0), 57 | ), 58 | onPressed: onPressed, 59 | ), 60 | ); 61 | } 62 | 63 | Widget _buildChildWidget() { 64 | if (isLoading) { 65 | return AppCircularProgressIndicator(color: Colors.white); 66 | } else { 67 | return Row( 68 | mainAxisAlignment: MainAxisAlignment.center, 69 | children: [ 70 | leadingIcon ?? Container(), 71 | title != null 72 | ? Text( 73 | title!, 74 | style: textStyle ?? TextStyle(fontSize: 16, fontWeight: FontWeight.w800, color: Colors.red), 75 | ) 76 | : Container(), 77 | trailingIcon ?? Container(), 78 | ], 79 | ); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/ui/widgets/buttons/app_icon_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_colors.dart'; 3 | 4 | import 'app_button.dart'; 5 | 6 | class AppIconButton extends AppButton { 7 | AppIconButton({ 8 | String? title, 9 | Widget? leadingIcon, 10 | Widget? trailingIcon, 11 | bool isLoading = false, 12 | TextStyle? textStyle, 13 | Color? backgroundColor, 14 | VoidCallback? onPressed, 15 | }) : super( 16 | title: title, 17 | leadingIcon: leadingIcon, 18 | trailingIcon: trailingIcon, 19 | isLoading: isLoading, 20 | onPressed: onPressed, 21 | textStyle: textStyle, 22 | backgroundColor: backgroundColor, 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /lib/ui/widgets/buttons/app_tint_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_colors.dart'; 3 | 4 | import 'app_button.dart'; 5 | 6 | class AppTintButton extends AppButton { 7 | AppTintButton({ 8 | @required String? title, 9 | bool isLoading = false, 10 | VoidCallback? onPressed, 11 | }) : super( 12 | title: title, 13 | isLoading: isLoading, 14 | onPressed: onPressed, 15 | textStyle: TextStyle( 16 | fontSize: 16, 17 | color: Colors.white, 18 | fontWeight: FontWeight.bold, 19 | ), 20 | backgroundColor: AppColors.buttonBGTint, 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /lib/ui/widgets/buttons/app_white_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_colors.dart'; 3 | 4 | import 'app_button.dart'; 5 | 6 | class AppWhiteButton extends AppButton { 7 | AppWhiteButton({ 8 | @required String? title, 9 | bool isLoading = false, 10 | VoidCallback? onPressed, 11 | }) : super( 12 | title: title, 13 | isLoading: isLoading, 14 | onPressed: onPressed, 15 | textStyle: TextStyle( 16 | fontSize: 16, 17 | color: AppColors.secondary, 18 | fontWeight: FontWeight.bold, 19 | ), 20 | backgroundColor: Colors.white, 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /lib/ui/widgets/empty_list_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_text_styles.dart'; 3 | 4 | class EmptyListWidget extends StatelessWidget { 5 | final String text; 6 | final RefreshCallback? onRefresh; 7 | 8 | EmptyListWidget({this.text = 'Không có data', this.onRefresh}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | child: RefreshIndicator( 14 | child: ListView.builder( 15 | itemBuilder: (context, index) { 16 | return Container( 17 | height: 200, 18 | width: double.infinity, 19 | child: Center( 20 | child: Text( 21 | text, 22 | style: AppTextStyle.greyS18W800, 23 | ), 24 | ), 25 | ); 26 | }, 27 | itemCount: 1, 28 | ), 29 | onRefresh: onRefresh ?? _onRefreshData), 30 | ); 31 | } 32 | 33 | Future _onRefreshData() async {} 34 | } 35 | -------------------------------------------------------------------------------- /lib/ui/widgets/error_list_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_text_styles.dart'; 3 | 4 | class ErrorListWidget extends StatelessWidget { 5 | final String text; 6 | final RefreshCallback? onRefresh; 7 | 8 | ErrorListWidget({this.text = 'Đã xảy ra lỗi', this.onRefresh}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | child: RefreshIndicator( 14 | child: ListView.builder( 15 | itemBuilder: (context, index) { 16 | return Container( 17 | height: 200, 18 | width: double.infinity, 19 | child: Center( 20 | child: Text( 21 | text, 22 | style: AppTextStyle.greyS18W800, 23 | ), 24 | ), 25 | ); 26 | }, 27 | itemCount: 1, 28 | ), 29 | onRefresh: onRefresh ?? _onRefreshData), 30 | ); 31 | } 32 | 33 | Future _onRefreshData() async {} 34 | } 35 | -------------------------------------------------------------------------------- /lib/ui/widgets/images/app_cache_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_app/common/app_colors.dart'; 5 | import 'package:flutter_app/common/app_images.dart'; 6 | 7 | class AppCacheImage extends StatelessWidget { 8 | final String url; 9 | final double? width; 10 | final double? height; 11 | final double? borderRadius; 12 | final BoxFit fit; 13 | 14 | AppCacheImage({ 15 | this.url = "", 16 | this.width, 17 | this.height, 18 | this.borderRadius, 19 | this.fit = BoxFit.cover, 20 | }); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | bool isValidUrl = Uri.tryParse(url)?.isAbsolute == true; 25 | return Container( 26 | width: width ?? double.infinity, 27 | height: height ?? double.infinity, 28 | child: isValidUrl 29 | ? ClipRRect( 30 | child: CachedNetworkImage( 31 | imageUrl: url, 32 | progressIndicatorBuilder: (context, url, downloadProgress) { 33 | return Center( 34 | child: Container( 35 | width: 24, 36 | height: 24, 37 | child: CircularProgressIndicator( 38 | value: downloadProgress.progress, 39 | backgroundColor: Colors.white, 40 | valueColor: AlwaysStoppedAnimation(AppColors.accent), 41 | ), 42 | ), 43 | ); 44 | }, 45 | errorWidget: (context, url, error) { 46 | return Image.network( 47 | url, 48 | errorBuilder: (context, error, stackTrace) => _buildPlaceHolderImage(), 49 | fit: fit, 50 | ); 51 | }, 52 | fit: fit, 53 | ), 54 | borderRadius: BorderRadius.circular(borderRadius ?? 0), 55 | ) 56 | : _buildPlaceHolderImage(), 57 | decoration: BoxDecoration( 58 | color: Colors.grey, 59 | borderRadius: BorderRadius.circular(borderRadius ?? 0), 60 | ), 61 | ); 62 | } 63 | 64 | Widget _buildPlaceHolderImage() { 65 | return ClipRRect( 66 | borderRadius: BorderRadius.circular(borderRadius ?? 0), 67 | child: Container( 68 | color: Color(0xFFe6e6e6), 69 | child: Center( 70 | child: Image.asset( 71 | AppImages.bgImagePlaceholder, 72 | fit: BoxFit.fitHeight, 73 | ), 74 | ), 75 | ), 76 | ); 77 | } 78 | } 79 | 80 | class AppCircleAvatar extends StatelessWidget { 81 | final String url; 82 | final double? size; 83 | 84 | AppCircleAvatar({this.url = "", this.size}); 85 | 86 | @override 87 | Widget build(BuildContext context) { 88 | bool isValidUrl = Uri.tryParse(url)?.isAbsolute == true; 89 | return Container( 90 | width: size ?? double.infinity, 91 | height: size ?? double.infinity, 92 | child: isValidUrl 93 | ? ClipRRect( 94 | child: CachedNetworkImage( 95 | imageUrl: url, 96 | progressIndicatorBuilder: (context, url, downloadProgress) { 97 | return Container( 98 | width: size, 99 | height: size, 100 | child: CircularProgressIndicator( 101 | value: downloadProgress.progress, 102 | strokeWidth: 2, 103 | ), 104 | ); 105 | }, 106 | errorWidget: (context, url, error) { 107 | return Container( 108 | width: double.infinity, 109 | height: double.infinity, 110 | child: Image.asset( 111 | AppImages.icAvatar, 112 | fit: BoxFit.cover, 113 | ), 114 | ); 115 | }, 116 | fit: BoxFit.fill, 117 | ), 118 | borderRadius: BorderRadius.circular((size ?? 0) / 2), 119 | ) 120 | : Container( 121 | width: double.infinity, 122 | height: double.infinity, 123 | child: Image.asset( 124 | AppImages.icAvatar, 125 | fit: BoxFit.cover, 126 | ), 127 | ), 128 | decoration: BoxDecoration( 129 | color: Colors.grey, 130 | borderRadius: BorderRadius.circular((size ?? 0) / 2), 131 | ), 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/ui/widgets/images/app_circle_avatar.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_app/common/app_images.dart'; 5 | 6 | class AppCircleAvatar extends StatelessWidget { 7 | final String url; 8 | final double? size; 9 | 10 | AppCircleAvatar({this.url = "", this.size}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | bool isValidUrl = Uri.tryParse(url)?.isAbsolute == true; 15 | return Container( 16 | width: size ?? double.infinity, 17 | height: size ?? double.infinity, 18 | child: isValidUrl 19 | ? ClipRRect( 20 | child: CachedNetworkImage( 21 | imageUrl: url, 22 | progressIndicatorBuilder: (context, url, downloadProgress) { 23 | return Container( 24 | width: size, 25 | height: size, 26 | child: CircularProgressIndicator( 27 | value: downloadProgress.progress, 28 | strokeWidth: 2, 29 | ), 30 | ); 31 | }, 32 | errorWidget: (context, url, error) { 33 | return Container( 34 | width: double.infinity, 35 | height: double.infinity, 36 | child: Image.asset( 37 | AppImages.icAvatar, 38 | fit: BoxFit.cover, 39 | ), 40 | ); 41 | }, 42 | fit: BoxFit.fill, 43 | ), 44 | borderRadius: BorderRadius.circular((size ?? 0) / 2), 45 | ) 46 | : Container( 47 | width: double.infinity, 48 | height: double.infinity, 49 | child: Image.asset( 50 | AppImages.icAvatar, 51 | fit: BoxFit.cover, 52 | ), 53 | ), 54 | decoration: BoxDecoration( 55 | color: Colors.grey, 56 | borderRadius: BorderRadius.circular((size ?? 0) / 2), 57 | ), 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/ui/widgets/input/app_email_input.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/utils/utils.dart'; 3 | 4 | import 'app_label_text_field.dart'; 5 | 6 | class AppEmailInput extends AppLabelTextField { 7 | AppEmailInput({ 8 | String? highlightText, 9 | TextStyle? labelStyle, 10 | TextStyle? textStyle, 11 | TextEditingController? textEditingController, 12 | ValueChanged? onChanged, 13 | bool enabled = true, 14 | }) : super( 15 | textEditingController: textEditingController, 16 | onChanged: onChanged, 17 | labelText: "Email", 18 | labelStyle: labelStyle, 19 | textStyle: textStyle, 20 | hintText: "", 21 | highlightText: highlightText ?? "*", 22 | textInputType: TextInputType.emailAddress, 23 | enabled: enabled, 24 | validator: (text) { 25 | if (Utils.isEmail(text ?? "") || (text ?? "").isEmpty) { 26 | return ""; 27 | } else { 28 | return "Email invalid"; 29 | } 30 | }, 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /lib/ui/widgets/input/app_label_text_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_app/common/app_colors.dart'; 4 | import 'package:flutter_app/common/app_text_styles.dart'; 5 | 6 | class AppLabelTextField extends StatelessWidget { 7 | final String labelText; 8 | final TextStyle? labelStyle; 9 | final String highlightText; 10 | final Widget? suffixIcon; 11 | final BoxConstraints? suffixIconConstraints; 12 | final TextEditingController? textEditingController; 13 | final TextStyle? textStyle; 14 | final String hintText; 15 | final TextStyle? hintStyle; 16 | final ValueChanged? onChanged; 17 | final ValueChanged? onSubmitted; 18 | final TextInputType textInputType; 19 | final FormFieldValidator? validator; 20 | final List? inputFormatters; 21 | final bool enabled; 22 | final int? maxLength; 23 | 24 | AppLabelTextField({ 25 | this.labelText = "", 26 | this.labelStyle, 27 | this.highlightText = "*", 28 | this.suffixIcon, 29 | this.suffixIconConstraints, 30 | this.textEditingController, 31 | this.textStyle, 32 | this.hintText = "", 33 | this.hintStyle, 34 | this.onChanged, 35 | this.onSubmitted, 36 | this.textInputType = TextInputType.text, 37 | this.validator, 38 | this.inputFormatters, 39 | this.enabled = true, 40 | this.maxLength, 41 | }); 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Container( 46 | child: Column( 47 | crossAxisAlignment: CrossAxisAlignment.start, 48 | children: [ 49 | Container( 50 | child: (highlightText != "" && highlightText.isNotEmpty) 51 | ? RichText( 52 | text: TextSpan(children: [ 53 | TextSpan( 54 | text: labelText, 55 | style: labelStyle ?? AppTextStyle.blackS12, 56 | ), 57 | TextSpan( 58 | text: highlightText, 59 | style: AppTextStyle.blackS12.copyWith(color: Colors.red), 60 | ) 61 | ]), 62 | ) 63 | : Text(labelText, style: labelStyle ?? AppTextStyle.blackS12), 64 | ), 65 | TextField( 66 | enabled: enabled, 67 | onSubmitted: onSubmitted, 68 | onChanged: onChanged, 69 | controller: textEditingController, 70 | style: textStyle ?? AppTextStyle.blackS16, 71 | maxLines: 1, 72 | maxLength: maxLength, 73 | decoration: InputDecoration( 74 | enabledBorder: UnderlineInputBorder( 75 | borderSide: BorderSide(color: AppColors.textFieldEnabledBorder), 76 | ), 77 | focusedBorder: UnderlineInputBorder( 78 | borderSide: BorderSide(color: AppColors.textFieldFocusedBorder), 79 | ), 80 | disabledBorder: UnderlineInputBorder( 81 | borderSide: BorderSide(color: AppColors.textFieldDisabledBorder), 82 | ), 83 | fillColor: Colors.white, 84 | hintStyle: hintStyle ?? AppTextStyle.greyS16, 85 | hintText: hintText, 86 | isDense: true, 87 | contentPadding: EdgeInsets.only(top: 8, bottom: 12), 88 | suffixIcon: suffixIcon, 89 | suffixIconConstraints: suffixIconConstraints ?? BoxConstraints(maxHeight: 32, maxWidth: 32), 90 | counterText: "", 91 | ), 92 | cursorColor: AppColors.textFieldCursor, 93 | keyboardType: textInputType, 94 | inputFormatters: inputFormatters, 95 | ), 96 | textEditingController != null 97 | ? ValueListenableBuilder( 98 | valueListenable: textEditingController!, 99 | builder: (context, TextEditingValue controller, child) { 100 | final isValid = validator?.call(controller.text) ?? ""; 101 | return Column( 102 | children: [ 103 | SizedBox(height: 2), 104 | Text( 105 | isValid, 106 | style: AppTextStyle.blackS12.copyWith(color: Colors.red), 107 | ), 108 | SizedBox(height: 12), 109 | ], 110 | ); 111 | }, 112 | ) 113 | : Container(), 114 | ], 115 | ), 116 | ); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lib/ui/widgets/input/app_password_input.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_colors.dart'; 3 | import 'package:flutter_app/common/app_images.dart'; 4 | import 'package:flutter_app/common/app_text_styles.dart'; 5 | 6 | class ObscureTextController extends ValueNotifier { 7 | ObscureTextController({bool obscureText = true}) : super(obscureText); 8 | 9 | bool get date => value; 10 | 11 | set date(bool obscureText) { 12 | value = obscureText; 13 | } 14 | } 15 | 16 | class AppPasswordInput extends StatelessWidget { 17 | final String labelText; 18 | final TextStyle? labelStyle; 19 | final String highlightText; 20 | final Widget? suffixIcon; 21 | final ObscureTextController? obscureTextController; 22 | final TextEditingController? textEditingController; 23 | final TextStyle? textStyle; 24 | final String hintText; 25 | final TextStyle? hintStyle; 26 | final ValueChanged? onChanged; 27 | final ValueChanged? onSubmitted; 28 | final TextInputType textInputType; 29 | final FocusNode? passwordFocusNode; 30 | 31 | AppPasswordInput({ 32 | this.labelText = "Mật khẩu", 33 | this.labelStyle, 34 | this.highlightText = "*", 35 | this.suffixIcon, 36 | this.obscureTextController, 37 | this.textEditingController, 38 | this.textStyle, 39 | this.hintText = "", 40 | this.hintStyle, 41 | this.onChanged, 42 | this.onSubmitted, 43 | this.textInputType = TextInputType.text, 44 | this.passwordFocusNode, 45 | }); 46 | 47 | Widget build(BuildContext context) { 48 | return Container( 49 | child: Column( 50 | crossAxisAlignment: CrossAxisAlignment.start, 51 | children: [ 52 | Container( 53 | child: RichText( 54 | text: TextSpan(children: [ 55 | TextSpan( 56 | text: labelText, 57 | style: labelStyle ?? AppTextStyle.blackS12, 58 | ), 59 | TextSpan( 60 | text: highlightText, 61 | style: AppTextStyle.blackS12.copyWith(color: Colors.red), 62 | ) 63 | ]), 64 | ), 65 | ), 66 | Stack( 67 | children: [ 68 | ValueListenableBuilder( 69 | valueListenable: obscureTextController!, 70 | child: Container(), 71 | builder: (context, bool obscureText, child) { 72 | return TextField( 73 | onSubmitted: onSubmitted, 74 | onChanged: onChanged, 75 | controller: textEditingController, 76 | focusNode: passwordFocusNode, 77 | style: textStyle ?? AppTextStyle.blackS16, 78 | maxLines: 1, 79 | decoration: InputDecoration( 80 | enabledBorder: UnderlineInputBorder( 81 | borderSide: BorderSide(color: AppColors.textFieldEnabledBorder), 82 | ), 83 | focusedBorder: UnderlineInputBorder( 84 | borderSide: BorderSide(color: AppColors.textFieldFocusedBorder), 85 | ), 86 | disabledBorder: UnderlineInputBorder( 87 | borderSide: BorderSide(color: AppColors.textFieldDisabledBorder), 88 | ), 89 | fillColor: Colors.white, 90 | hintStyle: hintStyle ?? AppTextStyle.greyS16, 91 | hintText: hintText, 92 | isDense: true, 93 | contentPadding: EdgeInsets.only(top: 8, bottom: 12), 94 | suffixIcon: Image.asset( 95 | AppImages.icEyeOpen, 96 | fit: BoxFit.fitWidth, 97 | color: Colors.transparent, 98 | ), 99 | suffixIconConstraints: BoxConstraints(maxHeight: 32, maxWidth: 32), 100 | ), 101 | cursorColor: AppColors.textFieldCursor, 102 | keyboardType: TextInputType.visiblePassword, 103 | obscureText: obscureText, 104 | ); 105 | }, 106 | ), 107 | ValueListenableBuilder( 108 | valueListenable: obscureTextController!, 109 | child: Container(), 110 | builder: (context, bool obscureText, child) { 111 | return _buildSuffixIcon(obscureText); 112 | }, 113 | ) 114 | ], 115 | ), 116 | textEditingController != null 117 | ? ValueListenableBuilder( 118 | valueListenable: textEditingController!, 119 | builder: (context, TextEditingValue controller, child) { 120 | final isValid = _validatePassword(controller.text); 121 | return Column( 122 | children: [ 123 | SizedBox(height: 2), 124 | Text( 125 | isValid, 126 | style: AppTextStyle.blackS12.copyWith(color: Colors.red), 127 | ), 128 | SizedBox(height: 8), 129 | ], 130 | ); 131 | }, 132 | ) 133 | : Container(), 134 | ], 135 | ), 136 | ); 137 | } 138 | 139 | Widget _buildSuffixIcon(bool obscureText) { 140 | return Align( 141 | alignment: Alignment.topRight, 142 | child: GestureDetector( 143 | onTap: () { 144 | Future.delayed(Duration.zero, () { 145 | passwordFocusNode?.unfocus(); 146 | }); 147 | obscureTextController?.value = !obscureText; 148 | }, 149 | child: Container( 150 | height: 34, 151 | width: 20, 152 | color: Colors.transparent, 153 | alignment: Alignment.centerRight, 154 | child: obscureText 155 | ? Image.asset( 156 | AppImages.icEyeClose, 157 | fit: BoxFit.fitWidth, 158 | width: 18, 159 | height: 24, 160 | ) 161 | : Image.asset( 162 | AppImages.icEyeOpen, 163 | fit: BoxFit.fitWidth, 164 | width: 18, 165 | height: 24, 166 | ), 167 | ), 168 | ), 169 | ); 170 | } 171 | 172 | String _validatePassword(String text) { 173 | return ""; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /lib/ui/widgets/input/app_phone_input.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/utils/utils.dart'; 3 | 4 | import 'app_label_text_field.dart'; 5 | 6 | class AppPhoneInput extends AppLabelTextField { 7 | AppPhoneInput({ 8 | String? highlightText, 9 | String? labelText, 10 | TextStyle? labelStyle, 11 | TextEditingController? textEditingController, 12 | ValueChanged? onChanged, 13 | bool enabled = true, 14 | }) : super( 15 | textEditingController: textEditingController, 16 | onChanged: onChanged, 17 | labelText: labelText ?? "Số điện thoại", 18 | labelStyle: labelStyle, 19 | hintText: "", 20 | highlightText: highlightText ?? "*", 21 | textInputType: TextInputType.phone, 22 | enabled: enabled, 23 | validator: (text) { 24 | if (Utils.isPhoneNumber(text ?? "") || (text ?? "").isEmpty) { 25 | return ""; 26 | } else { 27 | return "Số điện thoại không hợp lệ"; 28 | } 29 | }, 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /lib/ui/widgets/loading_more_row_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_colors.dart'; 3 | 4 | class LoadingMoreRowWidget extends StatelessWidget { 5 | final double height; 6 | final Color color; 7 | 8 | LoadingMoreRowWidget({this.height = 80, this.color = AppColors.secondary}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | height: height, 14 | child: Container( 15 | alignment: Alignment.center, 16 | child: Container( 17 | width: 24, 18 | height: 24, 19 | child: CircularProgressIndicator( 20 | backgroundColor: color, 21 | valueColor: AlwaysStoppedAnimation(AppColors.secondary), 22 | ), 23 | ), 24 | ), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/ui/widgets/loading_row_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_shadows.dart'; 3 | import 'package:shimmer/shimmer.dart'; 4 | 5 | class LoadingRowWidget extends StatelessWidget { 6 | final EdgeInsets? padding; 7 | final double height; 8 | 9 | static final _defaultPadding = EdgeInsets.symmetric( 10 | horizontal: 20, 11 | vertical: 6, 12 | ); 13 | 14 | LoadingRowWidget({this.padding, this.height = 100}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | var padding = this.padding ?? _defaultPadding; 19 | return Container( 20 | height: height, 21 | padding: padding, 22 | child: Container( 23 | child: Shimmer.fromColors( 24 | baseColor: Colors.grey[350]!, 25 | highlightColor: Colors.grey[100]!, 26 | child: Container( 27 | height: double.infinity, 28 | width: double.infinity, 29 | decoration: BoxDecoration( 30 | borderRadius: BorderRadius.circular(8), 31 | color: Colors.white, 32 | )), 33 | ), 34 | decoration: BoxDecoration(borderRadius: BorderRadius.circular(8), boxShadow: AppShadow.boxShadow), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/ui/widgets/pickers/app_label_date_picker.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_colors.dart'; 3 | import 'package:flutter_app/common/app_text_styles.dart'; 4 | import 'package:flutter_app/ui/commons/app_dialog.dart'; 5 | import 'package:intl/intl.dart'; 6 | 7 | class DatePickerController extends ValueNotifier { 8 | DatePickerController({DateTime? dateTime}) : super(dateTime); 9 | 10 | DateTime? get date => value; 11 | 12 | set date(DateTime? newDate) { 13 | value = newDate; 14 | } 15 | } 16 | 17 | class AppLabelDatePicker extends StatelessWidget { 18 | final DatePickerController? controller; 19 | final String dateFormat; 20 | final DateTime? minTime; 21 | final DateTime? maxTime; 22 | final ValueChanged? onChanged; 23 | 24 | final String labelText; 25 | final TextStyle? labelStyle; 26 | final String highlightText; 27 | final Widget? suffixIcon; 28 | final TextStyle? textStyle; 29 | final String hintText; 30 | final TextStyle? hintStyle; 31 | final bool enabled; 32 | 33 | AppLabelDatePicker({ 34 | this.dateFormat = "dd/MM/yyyy", 35 | this.minTime, 36 | this.maxTime, 37 | this.controller, 38 | this.labelText = "", 39 | this.labelStyle, 40 | this.highlightText = "*", 41 | this.suffixIcon, 42 | this.textStyle, 43 | this.hintText = "", 44 | this.hintStyle, 45 | this.onChanged, 46 | this.enabled = true, 47 | }); 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return Container( 52 | child: Column( 53 | crossAxisAlignment: CrossAxisAlignment.start, 54 | children: [ 55 | Container( 56 | child: RichText( 57 | text: TextSpan(children: [ 58 | TextSpan( 59 | text: labelText ?? "", 60 | style: labelStyle ?? AppTextStyle.blackS12, 61 | ), 62 | TextSpan( 63 | text: highlightText ?? "*", 64 | style: AppTextStyle.blackS12.copyWith(color: Colors.red), 65 | ) 66 | ]), 67 | ), 68 | ), 69 | ValueListenableBuilder( 70 | valueListenable: controller!, 71 | child: Container(), 72 | builder: (context, DateTime? dateTime, child) { 73 | var dateString = ""; 74 | if ((dateFormat != null) && (dateTime != null)) { 75 | dateString = DateFormat(dateFormat).format(dateTime); 76 | } 77 | return GestureDetector( 78 | onTap: () { 79 | _showDatePicker(context: context); 80 | }, 81 | child: TextField( 82 | enabled: false, 83 | textInputAction: TextInputAction.search, 84 | controller: TextEditingController(text: dateString), 85 | style: textStyle ?? AppTextStyle.blackS16, 86 | maxLines: 1, 87 | decoration: InputDecoration( 88 | enabledBorder: UnderlineInputBorder( 89 | borderSide: BorderSide(color: AppColors.textFieldEnabledBorder), 90 | ), 91 | focusedBorder: UnderlineInputBorder( 92 | borderSide: BorderSide(color: AppColors.textFieldFocusedBorder), 93 | ), 94 | disabledBorder: UnderlineInputBorder( 95 | borderSide: BorderSide(color: AppColors.textFieldDisabledBorder), 96 | ), 97 | fillColor: Colors.white, 98 | hintStyle: hintStyle ?? AppTextStyle.greyS16, 99 | hintText: hintText ?? "", 100 | isDense: true, 101 | contentPadding: EdgeInsets.only(top: 8, bottom: 12), 102 | suffixIcon: suffixIcon ?? Icon(Icons.date_range_outlined), 103 | suffixIconConstraints: BoxConstraints(maxHeight: 32, maxWidth: 32), 104 | ), 105 | cursorColor: AppColors.textFieldCursor, 106 | ), 107 | ); 108 | }, 109 | ), 110 | SizedBox(height: 22), 111 | ], 112 | ), 113 | ); 114 | } 115 | 116 | _showDatePicker({ 117 | BuildContext? context, 118 | }) { 119 | if (!enabled) { 120 | return; 121 | } 122 | if (context == null) { 123 | return; 124 | } 125 | AppDialog.showDatePicker( 126 | context, 127 | maxTime: maxTime, 128 | minTime: minTime, 129 | onConfirm: (dateTime) { 130 | onChanged?.call(dateTime); 131 | controller?.date = dateTime; 132 | }, 133 | currentTime: controller?.date ?? DateTime.now(), 134 | ); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /lib/ui/widgets/shimmer/app_shimmer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shimmer/shimmer.dart'; 3 | 4 | class AppShimmer extends StatelessWidget { 5 | final double width; 6 | final double height; 7 | final double cornerRadius; 8 | final EdgeInsets margin; 9 | final EdgeInsets padding; 10 | final Color? baseColor; 11 | final Color? highlightColor; 12 | 13 | AppShimmer({ 14 | this.width = double.infinity, 15 | this.height = double.infinity, 16 | this.cornerRadius = 0, 17 | this.margin = EdgeInsets.zero, 18 | this.padding = EdgeInsets.zero, 19 | this.baseColor, 20 | this.highlightColor, 21 | }); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Shimmer.fromColors( 26 | baseColor: baseColor ?? Colors.grey[350]!, 27 | highlightColor: highlightColor ?? Colors.grey[100]!, 28 | child: Container( 29 | width: width, 30 | height: height, 31 | margin: margin, 32 | padding: padding, 33 | child: Container( 34 | height: double.infinity, 35 | width: double.infinity, 36 | decoration: BoxDecoration( 37 | color: Colors.white, 38 | borderRadius: BorderRadius.all( 39 | Radius.circular(cornerRadius), 40 | ), 41 | ), 42 | ), 43 | ), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/ui/widgets/tabs/app_tab_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_app/common/app_colors.dart'; 3 | import 'package:flutter_app/common/app_shadows.dart'; 4 | import 'package:flutter_app/common/app_text_styles.dart'; 5 | 6 | /// tabController = TabController(length: 2, vsync: this) 7 | /// Page with TickerProviderStateMixin 8 | 9 | class AppTabBar extends StatelessWidget { 10 | final TabController? tabController; 11 | final List tabItems; 12 | 13 | AppTabBar({this.tabController, this.tabItems = const []}); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | height: 36, 19 | child: TabBar( 20 | controller: tabController, 21 | tabs: buildTabItems(), 22 | labelStyle: AppTextStyle.blackS14, 23 | unselectedLabelStyle: AppTextStyle.whiteS14, 24 | labelColor: Colors.white, 25 | unselectedLabelColor: AppColors.textWhite, 26 | indicator: BoxDecoration( 27 | color: AppColors.secondary, 28 | borderRadius: BorderRadius.all(Radius.circular(18)), 29 | boxShadow: AppShadow.boxShadow, 30 | ), 31 | indicatorWeight: 0, 32 | ), 33 | decoration: BoxDecoration( 34 | color: Colors.white, 35 | borderRadius: BorderRadius.all(Radius.circular(19)), 36 | boxShadow: AppShadow.boxShadow, 37 | ), 38 | ); 39 | } 40 | 41 | List buildTabItems() { 42 | List items = []; 43 | for (int i = 0; i < (tabItems ?? []).length; i++) { 44 | items.add(buildTabItem(tabItems[i], i)); 45 | } 46 | return items; 47 | } 48 | 49 | Widget buildTabItem(String title, int index) { 50 | return Text( 51 | title, 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/ui/widgets/textfields/app_text_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppTextFieldWidget extends StatelessWidget { 4 | final TextEditingController? inputController; 5 | final ValueChanged? onChanged; 6 | final TextInputType? textInputType; 7 | 8 | AppTextFieldWidget({this.inputController, this.onChanged, this.textInputType}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | height: 40, 14 | child: TextFormField( 15 | controller: inputController, 16 | decoration: InputDecoration( 17 | contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 0), 18 | border: OutlineInputBorder( 19 | borderRadius: BorderRadius.all( 20 | Radius.circular(12), 21 | ), 22 | ), 23 | ), 24 | keyboardType: textInputType, 25 | onChanged: onChanged, 26 | style: TextStyle(fontSize: 16, color: Colors.black), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/utils/date_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_app/configs/app_configs.dart'; 2 | import 'package:intl/intl.dart'; 3 | 4 | class DateUtils { 5 | static DateTime? fromString(String date, {String? format}) { 6 | return DateTimeExtension.fromString(date, format: format); 7 | } 8 | 9 | static String toDateString(DateTime dateTime, {String format = AppConfigs.dateDisplayFormat}) { 10 | try { 11 | return DateFormat(format).format(dateTime.toLocal()); 12 | } catch (e) { 13 | return ''; 14 | } 15 | } 16 | 17 | static String toDateTimeString(DateTime dateTime, {String format = AppConfigs.dateTimeDisplayFormat}) { 18 | try { 19 | return DateFormat(format).format(dateTime.toLocal()); 20 | } catch (e) { 21 | return ''; 22 | } 23 | } 24 | 25 | static String toDateAPIString(DateTime dateTime, {String format = AppConfigs.dateAPIFormat}) { 26 | try { 27 | return DateFormat(format).format(dateTime); 28 | } catch (e) { 29 | return ''; 30 | } 31 | } 32 | 33 | static String toDateTimeAPIString(DateTime dateTime, {String format = AppConfigs.dateTimeAPIFormat}) { 34 | try { 35 | return dateTime.toIso8601String(); 36 | } catch (e) { 37 | return ''; 38 | } 39 | } 40 | 41 | static DateTime startOfDay(DateTime dateTime) { 42 | return DateTime(dateTime.year, dateTime.month, dateTime.day, 0, 0, 0); 43 | } 44 | 45 | static DateTime endOfDay(DateTime dateTime) { 46 | return DateTime(dateTime.year, dateTime.month, dateTime.day, 23, 59, 59); 47 | } 48 | } 49 | 50 | ///Method 51 | extension DateTimeExtension on DateTime { 52 | static DateTime? fromString(String date, {String? format}) { 53 | if ((format ?? "").isNotEmpty) { 54 | try { 55 | return DateFormat(format).parse(date); 56 | } catch (e) {} 57 | } 58 | try { 59 | return DateTime.parse(date); 60 | } catch (e) {} 61 | // 62 | try { 63 | return DateFormat("yyyy/MM/dd").parse(date); 64 | } catch (e) {} 65 | return null; 66 | } 67 | 68 | String toDateString({String format = AppConfigs.dateDisplayFormat}) { 69 | try { 70 | return DateFormat(format).format(this); 71 | } catch (e) { 72 | return ''; 73 | } 74 | } 75 | 76 | String toDateTimeString({String format = AppConfigs.dateTimeDisplayFormat}) { 77 | try { 78 | return DateFormat(format).format(this.toLocal()); 79 | } catch (e) { 80 | return ''; 81 | } 82 | } 83 | 84 | String toDateAPIString({String format = AppConfigs.dateAPIFormat}) { 85 | try { 86 | return DateFormat(format).format(this); 87 | } catch (e) { 88 | return ''; 89 | } 90 | } 91 | 92 | String toDateTimeAPIString({String format = AppConfigs.dateTimeAPIFormat}) { 93 | try { 94 | return this.toIso8601String(); 95 | } catch (e) { 96 | return ''; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/utils/file_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class FileUtils{ 4 | static const double FILE_SIZE_LIMITED = 10; 5 | 6 | static double getFileSize(File file) { 7 | int sizeInBytes = file.lengthSync(); 8 | double sizeInMb = sizeInBytes / (1024 * 1024); 9 | return sizeInMb; 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /lib/utils/logger.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer' as developer; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:logger/logger.dart'; 5 | 6 | var logger = Logger(); 7 | var apiLogger = MyLogger(); 8 | 9 | class MyLogger { 10 | /// Log a message at level verbose. 11 | void v(dynamic message) { 12 | _print("🤍 VERBOSE: $message"); 13 | } 14 | 15 | /// Log a message at level debug. 16 | void d(dynamic message) { 17 | _print("💙 DEBUG: $message"); 18 | } 19 | 20 | /// Log a message at level info. 21 | void i(dynamic message) { 22 | _print("💚️ INFO: $message"); 23 | } 24 | 25 | /// Log a message at level warning. 26 | void w(dynamic message) { 27 | _print("💛 WARNING: $message"); 28 | } 29 | 30 | /// Log a message at level error. 31 | void e(dynamic message) { 32 | _print("❤️ ERROR: $message"); 33 | } 34 | 35 | void _print(dynamic message) { 36 | if (kDebugMode) { 37 | print("$message"); 38 | } 39 | } 40 | 41 | void _log(dynamic message) { 42 | if (kDebugMode) { 43 | developer.log("$message"); 44 | } 45 | } 46 | 47 | void log(dynamic message, 48 | {bool printFullText = false, StackTrace? stackTrace}) { 49 | if (printFullText) { 50 | _log(message); 51 | } else { 52 | _print(message); 53 | } 54 | if (stackTrace != null) { 55 | _print(stackTrace); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/utils/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class Utils { 4 | ///Search 5 | // static bool isTextContainKeyword({String text = "", String keyword = ""}) { 6 | // final newText = String.fromCharCodes(replaceCodeUnits(text.codeUnits)).toLowerCase(); 7 | // final newKeyword = String.fromCharCodes(replaceCodeUnits(keyword.codeUnits)).toLowerCase(); 8 | // final isContain = newText.contains(newKeyword); 9 | // return isContain; 10 | // } 11 | // 12 | // static launchPhoneCall({String phone}) async { 13 | // try { 14 | // await launch("tel:$phone"); 15 | // } catch (e) { 16 | // logger.e(e); 17 | // } 18 | // } 19 | // 20 | // static launchEmail({String email}) async { 21 | // try { 22 | // await launch(Uri( 23 | // scheme: 'mailto', 24 | // path: email, 25 | // ).toString()); 26 | // } catch (e) { 27 | // logger.e(e); 28 | // } 29 | // } 30 | 31 | /// Checks if string is email. 32 | static bool isEmail(String email) => GetUtils.isEmail(email); 33 | 34 | /// Checks if string is phone number. 35 | static bool isPhoneNumber(String email) => GetUtils.isPhoneNumber(email); 36 | 37 | /// Checks if string is URL. 38 | static bool isURL(String url) => GetUtils.isURL(url); 39 | } 40 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_app 2 | description: A new Flutter application. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.12.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | flutter_localizations: 27 | sdk: flutter 28 | 29 | #State management 30 | get: ^4.6.1 31 | 32 | #Utilities 33 | package_info_plus: ^1.4.0 34 | # permission_handler: ^9.2.0 35 | intl_utils: ^2.1.0 36 | logger: ^1.1.0 37 | 38 | #Store 39 | flutter_secure_storage: ^5.0.2 40 | shared_preferences: ^2.0.13 41 | 42 | #Network 43 | retrofit: ^2.0.0-beta1 44 | dio: ^4.0.0 45 | json_annotation: ^4.0.0 46 | 47 | #Custom UI 48 | flutter_datetime_picker: ^1.5.1 49 | shimmer: ^2.0.0 50 | 51 | #Dialog, etc 52 | cached_network_image: ^3.0.0 53 | 54 | # The following adds the Cupertino Icons font to your application. 55 | # Use with the CupertinoIcons class for iOS style icons. 56 | cupertino_icons: ^1.0.2 57 | 58 | dev_dependencies: 59 | flutter_test: 60 | sdk: flutter 61 | build_runner: ^2.0.4 62 | json_serializable: ^4.1.3 63 | retrofit_generator: ^2.0.0+1 64 | 65 | # For information on the generic Dart part of this file, see the 66 | # following page: https://dart.dev/tools/pub/pubspec 67 | 68 | # The following section is specific to Flutter. 69 | flutter: 70 | 71 | # The following line ensures that the Material Icons font is 72 | # included with your application, so that you can use the icons in 73 | # the material Icons class. 74 | uses-material-design: true 75 | 76 | # To add assets to your application, add an assets section, like this: 77 | # assets: 78 | # - images/a_dot_burr.jpeg 79 | # - images/a_dot_ham.jpeg 80 | assets: 81 | - assets/images/bg_image_placeholder.png 82 | - assets/images/ic_logo.png 83 | - assets/images/ic_logo_transparent.png 84 | - assets/images/ic_back.png 85 | - assets/images/ic_white_back.png 86 | - assets/images/ic_avatar.png 87 | - assets/images/ic_eye_close.png 88 | - assets/images/ic_eye_open.png 89 | - assets/images/ic_eye_open.png 90 | 91 | fonts: 92 | - family: Arimo 93 | fonts: 94 | - asset: assets/fonts/Arimo-Regular.ttf 95 | weight: 300 96 | - asset: assets/fonts/Arimo-Regular.ttf 97 | weight: 400 98 | - asset: assets/fonts/Arimo-Medium.ttf 99 | weight: 500 100 | - asset: assets/fonts/Arimo-SemiBold.ttf 101 | weight: 600 102 | - asset: assets/fonts/Arimo-Bold.ttf 103 | weight: 700 104 | - asset: assets/fonts/Arimo-Bold.ttf 105 | weight: 800 106 | - asset: assets/fonts/Arimo-Bold.ttf 107 | weight: 900 108 | 109 | # An image asset can refer to one or more resolution-specific "variants", see 110 | # https://flutter.dev/assets-and-images/#resolution-aware. 111 | 112 | # For details regarding adding assets from package dependencies, see 113 | # https://flutter.dev/assets-and-images/#from-packages 114 | 115 | # To add custom fonts to your application, add a fonts section here, 116 | # in this "flutter" section. Each entry in this list should have a 117 | # "family" key with the font family name, and a "fonts" key with a 118 | # list giving the asset and other descriptors for the font. For 119 | # example: 120 | # fonts: 121 | # - family: Schyler 122 | # fonts: 123 | # - asset: fonts/Schyler-Regular.ttf 124 | # - asset: fonts/Schyler-Italic.ttf 125 | # style: italic 126 | # - family: Trajan Pro 127 | # fonts: 128 | # - asset: fonts/TrajanPro.ttf 129 | # - asset: fonts/TrajanPro_Bold.ttf 130 | # weight: 700 131 | # 132 | # For details regarding fonts from package dependencies, 133 | # see https://flutter.dev/custom-fonts/#from-packages 134 | flutter_intl: 135 | enabled: true 136 | -------------------------------------------------------------------------------- /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 that Flutter provides. 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 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_app/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------