├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── flutter_clean_architecture │ │ │ │ └── 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 │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── architecture-proposal.png ├── core ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── README.md ├── lib │ ├── core.dart │ └── src │ │ ├── config │ │ └── app_config.dart │ │ ├── di │ │ └── locator.dart │ │ ├── error │ │ ├── exceptions.dart │ │ └── failures.dart │ │ └── localizations │ │ ├── language_vi.dart │ │ ├── languages.dart │ │ ├── localizations_delegate.dart │ │ └── strings.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── core_test.dart ├── data ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── README.md ├── lib │ ├── data.dart │ └── src │ │ ├── data_source │ │ ├── local │ │ │ └── shared_preferences_store.dart │ │ ├── remote │ │ │ ├── base │ │ │ │ └── api_client.dart │ │ │ ├── popup_api.dart │ │ │ ├── regions_api.dart │ │ │ └── screen_setting_api.dart │ │ └── service │ │ │ └── firebase_remote_config.dart │ │ ├── di │ │ └── locator.dart │ │ ├── models │ │ ├── list_response_model.dart │ │ ├── metadata_model.dart │ │ ├── popup_model.dart │ │ ├── region_model.dart │ │ └── screen_setting_model.dart │ │ └── repository │ │ ├── api_error_handle_repo_impl.dart │ │ ├── base │ │ └── base_repository.dart │ │ ├── internet_status_repo_impl.dart │ │ ├── popup_repo_impl.dart │ │ ├── region_repo_impl.dart │ │ └── screen_setting_repo_impl.dart ├── pubspec.lock ├── pubspec.yaml └── test │ ├── data_source │ └── remote │ │ └── screen_setting_api_test.dart │ ├── data_test.dart │ └── repository │ └── internet_status_test.dart ├── domain ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── README.md ├── lib │ ├── domain.dart │ └── src │ │ ├── common │ │ ├── error_type.dart │ │ └── result.dart │ │ ├── di │ │ └── locator.dart │ │ ├── entities │ │ ├── list_response.dart │ │ ├── metadata.dart │ │ ├── popup.dart │ │ ├── region.dart │ │ ├── screen_setting.dart │ │ └── validator.dart │ │ ├── repositories │ │ ├── api_error_handler_repo.dart │ │ ├── internet_status_repo.dart │ │ ├── popup_repo.dart │ │ ├── region_repo.dart │ │ └── screen_setting_repo.dart │ │ └── usecase │ │ ├── api_error_handler.dart │ │ ├── base │ │ └── use_case.dart │ │ ├── check_internet_status.dart │ │ ├── get_float_popup.dart │ │ ├── get_home_screen_setting.dart │ │ ├── get_region.dart │ │ ├── get_regions.dart │ │ ├── get_regions_local.dart │ │ ├── get_shopping_screen_setting.dart │ │ └── update_region.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── domain_test.dart ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ ├── development.xcscheme │ │ ├── production.xcscheme │ │ └── staging.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── main.dart ├── main.dev.dart ├── main.prod.dart ├── main.staging.dart └── presentation │ ├── common │ └── base_bloc.dart │ ├── di │ └── locator.dart │ ├── extension │ └── screen_setting.dart │ ├── routes │ ├── app_routes.dart │ └── routes.dart │ ├── screen │ ├── app │ │ ├── app.dart │ │ ├── app_bloc.dart │ │ └── app_state.dart │ ├── app_bottom_bar │ │ ├── app_bottom_bar.dart │ │ ├── app_bottom_bar_bloc.dart │ │ └── widget │ │ │ ├── custom_bottom_app_bar.dart │ │ │ └── custom_bottom_navigation_bar.dart │ ├── home │ │ ├── home_bloc.dart │ │ ├── home_screen.dart │ │ ├── home_setting_bloc.dart │ │ └── widget │ │ │ ├── home_bloc_container.dart │ │ │ ├── home_search_bar.dart │ │ │ └── home_sliver_app_bar.dart │ ├── popup │ │ ├── floating_popup │ │ │ ├── floating_popup.dart │ │ │ └── floating_popup_bloc.dart │ │ └── region │ │ │ ├── region_bloc.dart │ │ │ └── region_dialog.dart │ └── shopping │ │ ├── shopping_bloc.dart │ │ ├── shopping_screen.dart │ │ ├── shopping_setting_bloc.dart │ │ └── widget │ │ ├── shopping_search_bar.dart │ │ └── shopping_sliver_app_bar.dart │ ├── values │ ├── assets.dart │ ├── constant.dart │ ├── sizes.dart │ └── themes.dart │ └── widgets │ ├── choose_region.dart │ ├── custom_sliver_app_bar_delegate.dart │ └── search_bar.dart ├── plugin └── utils │ ├── .gitignore │ ├── .metadata │ ├── CHANGELOG.md │ ├── README.md │ ├── lib │ └── utils.dart │ ├── pubspec.lock │ ├── pubspec.yaml │ └── test │ └── utils_test.dart ├── pubspec.lock ├── pubspec.yaml ├── test └── widget_test.dart └── tool ├── coverage.sh └── generate_asset_metadata.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /.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: 4cc385b4b84ac2f816d939a49ea1f328c4e0b48e 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter Clean Architecture 2 | ``` 3 | - Flutter v2.5.0 4 | - Clean Architecture 5 | - Presentation use Bloc 6 | ``` 7 | 8 | # Technical architecture components 9 | - Clean Architecture 10 | - Dart rule analyze: pedantic 11 | - State management: flutter_bloc 12 | - Dependency injection: get_it 13 | - Network: dio 14 | - Unit test: Mockito 15 | 16 | ## Run 17 | ``` 18 | flutter clean 19 | flutter pub get 20 | flutter packages pub run build_runner watch 21 | 22 | flutter run --flavor development -t lib/main.dev.dart 23 | flutter run --flavor staging -t lib/main.staging.dart 24 | flutter run --flavor product -t lib/main.product.dart 25 | ``` 26 | ## Build Release 27 | 28 | - Android: 29 | ``` 30 | flutter build apk --flavor development -t lib/main.dev.dart 31 | flutter build apk --flavor staging -t lib/main.staging.dart 32 | flutter build apk --flavor product -t lib/main.product.dart 33 | ``` 34 | - iOS: 35 | ``` 36 | flutter build ios --flavor development -t lib/main.dev.dart 37 | flutter build ios --flavor staging -t lib/main.staging.dart 38 | flutter build ios --flavor product -t lib/main.product.dart 39 | ``` 40 | 41 | ## Refer 42 | - https://github.com/ResoCoder/flutter-tdd-clean-architecture-course 43 | - https://geekysingh.medium.com/clean-architecture-for-enterprise-flutter-application-dc254a71059 44 | - https://github.com/trunghieuvn/flutter-clean-architecture 45 | - https://github.com/vantrung8794/flutter_code_base 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | exclude: 3 | - build/** 4 | # strong-mode: 5 | # implicit-casts: false 6 | errors: 7 | avoid_as: error 8 | 9 | ##https://dart-lang.github.io/linter/lints/ 10 | linter: 11 | rules: 12 | # - always_put_control_body_on_new_line 13 | # - always_put_required_named_parameters_first 14 | # - always_specify_types 15 | - avoid_annotating_with_dynamic 16 | # - avoid_as 17 | # - avoid_bool_literals_in_conditional_expressions # under review (see #1068) 18 | # - avoid_catches_without_on_clauses 19 | - avoid_catching_errors 20 | # - avoid_classes_with_only_static_members 21 | # - avoid_double_and_int_checks # under review (see #1068) 22 | # - avoid_field_initializers_in_const_classes # under review (see #1068) 23 | # - avoid_function_literals_in_foreach_calls 24 | # - avoid_implementing_value_types 25 | # - avoid_js_rounded_ints # under review (see #1068) 26 | # - avoid_positional_boolean_parameters 27 | # - avoid_private_typedef_functions # under review (see #1068) 28 | - avoid_redundant_argument_values 29 | # - avoid_renaming_method_parameters # under review (see #1068) 30 | - avoid_returning_null 31 | - avoid_returning_null_for_future 32 | - avoid_returning_this 33 | - avoid_setters_without_getters 34 | # - avoid_single_cascade_in_expression_statements # under review (see #1068) 35 | - avoid_slow_async_io 36 | # - avoid_types_on_closure_parameters 37 | # - avoid_unused_constructor_parameters # under review (see #1068) 38 | # - avoid_void_async # under review (see #1068) 39 | - await_only_futures 40 | - camel_case_types 41 | - cancel_subscriptions 42 | # - cascade_invocations 43 | # - close_sinks # https://github.com/dart-lang/linter/issues/268 44 | - comment_references 45 | # - constant_identifier_names 46 | - control_flow_in_finally 47 | - directives_ordering 48 | - empty_statements 49 | # - file_names # under review (see #1068) 50 | - hash_and_equals 51 | - implementation_imports 52 | - invariant_booleans 53 | - iterable_contains_unrelated_type 54 | - join_return_with_assignment 55 | # - lines_longer_than_80_chars # under review (see #1068) 56 | - list_remove_unrelated_type 57 | - literal_only_boolean_expressions 58 | - no_adjacent_strings_in_list 59 | # - non_constant_identifier_names 60 | # - one_member_abstracts 61 | # - only_throw_errors 62 | - overridden_fields 63 | - package_api_docs 64 | - package_names 65 | - package_prefixed_library_names 66 | - parameter_assignments 67 | - prefer_asserts_in_initializer_lists 68 | # - prefer_bool_in_asserts # Deprecated 69 | # - prefer_const_constructors 70 | - prefer_const_constructors_in_immutables 71 | # - prefer_const_declarations # under review (see #1068) 72 | # - prefer_const_literals_to_create_immutables # under review (see #1068) 73 | - prefer_constructors_over_static_methods 74 | # - prefer_expression_function_bodies 75 | - prefer_final_fields 76 | # - prefer_final_locals 77 | - prefer_foreach 78 | # - prefer_function_declarations_over_variables 79 | - prefer_initializing_formals 80 | - prefer_inlined_adds 81 | - prefer_interpolation_to_compose_strings 82 | - prefer_is_not_operator 83 | # - prefer_mixin 84 | - prefer_null_aware_operators 85 | - prefer_relative_imports 86 | # - prefer_typing_uninitialized_variables # under review (see #1068) 87 | - prefer_void_to_null 88 | - provide_deprecation_message 89 | # - public_member_api_docs 90 | # - sort_constructors_first 91 | # - sort_pub_dependencies 92 | # - sort_unnamed_constructors_first 93 | # - super_goes_last 94 | - test_types_in_equals 95 | - throw_in_finally 96 | # - type_annotate_public_apis 97 | - unnecessary_brace_in_string_interps 98 | - unnecessary_getters_setters 99 | - unnecessary_lambdas 100 | - unnecessary_null_aware_assignments 101 | - unnecessary_overrides 102 | - unnecessary_parenthesis 103 | - unnecessary_statements 104 | - use_setters_to_change_properties 105 | - use_string_buffers 106 | - use_to_and_as_if_applicable 107 | - void_checks 108 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 30 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | applicationId "com.example.flutter_clean_architecture" 46 | minSdkVersion 16 47 | targetSdkVersion 30 48 | versionCode flutterVersionCode.toInteger() 49 | versionName flutterVersionName 50 | } 51 | 52 | flavorDimensions "env" 53 | productFlavors { 54 | development { 55 | dimension "env" 56 | applicationIdSuffix ".dev" 57 | versionNameSuffix "-dev" 58 | resValue "string", "app_name", "@string/app_name_dev" 59 | } 60 | staging { 61 | dimension "env" 62 | applicationIdSuffix ".staging" 63 | versionNameSuffix "-staging" 64 | resValue "string", "app_name", "@string/app_name_staging" 65 | } 66 | production { 67 | dimension "env" 68 | resValue "string", "app_name", "@string/app_name_release" 69 | } 70 | } 71 | } 72 | 73 | flutter { 74 | source '../..' 75 | } 76 | 77 | dependencies { 78 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 79 | } 80 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 9 | 16 | 20 | 23 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/flutter_clean_architecture/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.flutter_clean_architecture 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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Flutter 4 | Flutter Staging 5 | Flutter Dev 6 | -------------------------------------------------------------------------------- /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 | mavenCentral() 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 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /architecture-proposal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/architecture-proposal.png -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/ephemeral 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | 71 | # Exceptions to above rules. 72 | !**/ios/**/default.mode1v3 73 | !**/ios/**/default.mode2v3 74 | !**/ios/**/default.pbxuser 75 | !**/ios/**/default.perspectivev3 76 | /coverage/ 77 | -------------------------------------------------------------------------------- /core/.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: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | # core 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Dart 8 | [package](https://flutter.dev/developing-packages/), 9 | a library module containing code that can be shared easily across 10 | multiple Flutter or Dart projects. 11 | 12 | For help getting started with Flutter, view our 13 | [online documentation](https://flutter.dev/docs), which offers tutorials, 14 | samples, guidance on mobile development, and a full API reference. 15 | -------------------------------------------------------------------------------- /core/lib/core.dart: -------------------------------------------------------------------------------- 1 | library core; 2 | 3 | import 'src/config/app_config.dart'; 4 | import 'src/di/locator.dart'; 5 | 6 | export 'src/config/app_config.dart'; 7 | export 'src/localizations/languages.dart'; 8 | export 'src/localizations/localizations_delegate.dart'; 9 | export 'src/localizations/strings.dart'; 10 | 11 | class Core { 12 | static void init(Environment environment) { 13 | setupLocator(environment); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /core/lib/src/config/app_config.dart: -------------------------------------------------------------------------------- 1 | enum Environment { DEV, STAGING, PROD } 2 | 3 | class AppConfig { 4 | late Environment environment; 5 | late Map _config; 6 | 7 | String get baseUrl => _config[_Config.BASE_URL]; 8 | 9 | AppConfig(this.environment) { 10 | switch (environment) { 11 | case Environment.DEV: 12 | _config = _Config.devConstants; 13 | break; 14 | case Environment.STAGING: 15 | _config = _Config.stagingConstants; 16 | break; 17 | case Environment.PROD: 18 | _config = _Config.prodConstants; 19 | break; 20 | } 21 | } 22 | } 23 | 24 | class _Config { 25 | static const BASE_URL = 'BASE_URL'; 26 | 27 | static Map devConstants = { 28 | BASE_URL: 'https://api-dev', 29 | }; 30 | 31 | static Map stagingConstants = { 32 | BASE_URL: 'https://api-staging', 33 | }; 34 | 35 | static Map prodConstants = { 36 | BASE_URL: 'https://api-product', 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /core/lib/src/di/locator.dart: -------------------------------------------------------------------------------- 1 | import 'package:get_it/get_it.dart'; 2 | 3 | import '../config/app_config.dart'; 4 | import '../localizations/strings.dart'; 5 | 6 | final locator = GetIt.instance..allowReassignment = true; 7 | 8 | void setupLocator(Environment environment) { 9 | locator.registerSingleton(AppConfig(environment)); 10 | locator.registerLazySingleton(() => Strings()); 11 | } 12 | -------------------------------------------------------------------------------- /core/lib/src/error/exceptions.dart: -------------------------------------------------------------------------------- 1 | class ServerException implements Exception {} 2 | 3 | class CacheException implements Exception {} 4 | -------------------------------------------------------------------------------- /core/lib/src/error/failures.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class Failure extends Equatable { 4 | @override 5 | List get props => []; 6 | } 7 | 8 | class ServerFailure extends Failure {} 9 | 10 | class CacheFailure extends Failure {} 11 | -------------------------------------------------------------------------------- /core/lib/src/localizations/language_vi.dart: -------------------------------------------------------------------------------- 1 | import 'languages.dart'; 2 | 3 | class LanguageVi extends Languages { 4 | @override 5 | String get appName => 'Flutter'; 6 | 7 | @override 8 | String get coupon => 'Ưu đãi'; 9 | 10 | @override 11 | String get home => 'Trang chủ'; 12 | 13 | @override 14 | String get newFeed => 'Bài viết'; 15 | 16 | @override 17 | String get shopping => 'Mua sắm'; 18 | 19 | @override 20 | String get user => 'Cá nhân'; 21 | 22 | @override 23 | String get titleLocation => 'Khu vực'; 24 | } 25 | -------------------------------------------------------------------------------- /core/lib/src/localizations/languages.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | import 'language_vi.dart'; 4 | 5 | abstract class Languages { 6 | static Languages of(BuildContext context) { 7 | return Localizations.of(context, Languages) ?? LanguageVi(); 8 | } 9 | 10 | String get appName; 11 | 12 | String get home; 13 | 14 | String get coupon; 15 | 16 | String get shopping; 17 | 18 | String get newFeed; 19 | 20 | String get user; 21 | 22 | String get titleLocation; 23 | } 24 | -------------------------------------------------------------------------------- /core/lib/src/localizations/localizations_delegate.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | import 'language_vi.dart'; 4 | import 'languages.dart'; 5 | 6 | class AppLocalizationsDelegate extends LocalizationsDelegate { 7 | const AppLocalizationsDelegate._(); 8 | 9 | static const AppLocalizationsDelegate delegate = AppLocalizationsDelegate._(); 10 | 11 | static const List supportedLocales = const [ 12 | const Locale('vi', ''), 13 | ]; 14 | 15 | @override 16 | bool isSupported(Locale locale) => [ 17 | 'vi', 18 | ].contains(locale.languageCode); 19 | 20 | @override 21 | Future load(Locale locale) => _load(locale); 22 | 23 | static Future _load(Locale locale) async { 24 | switch (locale.languageCode) { 25 | case 'vi': 26 | return LanguageVi(); 27 | default: 28 | return LanguageVi(); 29 | } 30 | } 31 | 32 | @override 33 | bool shouldReload(LocalizationsDelegate old) => false; 34 | } 35 | -------------------------------------------------------------------------------- /core/lib/src/localizations/strings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | import 'languages.dart'; 4 | 5 | class Strings { 6 | static late BuildContext _context; 7 | 8 | static init(BuildContext context) { 9 | _context = context; 10 | } 11 | 12 | static Languages get tr => Languages.of(_context); 13 | } 14 | -------------------------------------------------------------------------------- /core/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | equatable: 47 | dependency: "direct main" 48 | description: 49 | name: equatable 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.0.3" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.2.0" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_localizations: 66 | dependency: "direct main" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | flutter_test: 71 | dependency: "direct dev" 72 | description: flutter 73 | source: sdk 74 | version: "0.0.0" 75 | get_it: 76 | dependency: "direct main" 77 | description: 78 | name: get_it 79 | url: "https://pub.dartlang.org" 80 | source: hosted 81 | version: "7.2.0" 82 | intl: 83 | dependency: transitive 84 | description: 85 | name: intl 86 | url: "https://pub.dartlang.org" 87 | source: hosted 88 | version: "0.17.0" 89 | matcher: 90 | dependency: transitive 91 | description: 92 | name: matcher 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "0.12.10" 96 | meta: 97 | dependency: transitive 98 | description: 99 | name: meta 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.7.0" 103 | path: 104 | dependency: transitive 105 | description: 106 | name: path 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "1.8.0" 110 | sky_engine: 111 | dependency: transitive 112 | description: flutter 113 | source: sdk 114 | version: "0.0.99" 115 | source_span: 116 | dependency: transitive 117 | description: 118 | name: source_span 119 | url: "https://pub.dartlang.org" 120 | source: hosted 121 | version: "1.8.1" 122 | stack_trace: 123 | dependency: transitive 124 | description: 125 | name: stack_trace 126 | url: "https://pub.dartlang.org" 127 | source: hosted 128 | version: "1.10.0" 129 | stream_channel: 130 | dependency: transitive 131 | description: 132 | name: stream_channel 133 | url: "https://pub.dartlang.org" 134 | source: hosted 135 | version: "2.1.0" 136 | string_scanner: 137 | dependency: transitive 138 | description: 139 | name: string_scanner 140 | url: "https://pub.dartlang.org" 141 | source: hosted 142 | version: "1.1.0" 143 | term_glyph: 144 | dependency: transitive 145 | description: 146 | name: term_glyph 147 | url: "https://pub.dartlang.org" 148 | source: hosted 149 | version: "1.2.0" 150 | test_api: 151 | dependency: transitive 152 | description: 153 | name: test_api 154 | url: "https://pub.dartlang.org" 155 | source: hosted 156 | version: "0.4.2" 157 | typed_data: 158 | dependency: transitive 159 | description: 160 | name: typed_data 161 | url: "https://pub.dartlang.org" 162 | source: hosted 163 | version: "1.3.0" 164 | vector_math: 165 | dependency: transitive 166 | description: 167 | name: vector_math 168 | url: "https://pub.dartlang.org" 169 | source: hosted 170 | version: "2.1.0" 171 | sdks: 172 | dart: ">=2.12.0 <3.0.0" 173 | flutter: ">=1.17.0" 174 | -------------------------------------------------------------------------------- /core/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: core 2 | description: A new Flutter project. 3 | version: 0.0.1 4 | publish_to: none 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | flutter_localizations: 14 | sdk: flutter 15 | 16 | get_it: 7.2.0 17 | equatable: 2.0.3 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | 23 | # The following section is specific to Flutter. 24 | flutter: 25 | 26 | -------------------------------------------------------------------------------- /core/test/core_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | test('adds one to input values', () {}); 5 | } 6 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/ephemeral 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | 71 | # Exceptions to above rules. 72 | !**/ios/**/default.mode1v3 73 | !**/ios/**/default.mode2v3 74 | !**/ios/**/default.pbxuser 75 | !**/ios/**/default.perspectivev3 76 | /coverage/ 77 | -------------------------------------------------------------------------------- /data/.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: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /data/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # data 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Dart 8 | [package](https://flutter.dev/developing-packages/), 9 | a library module containing code that can be shared easily across 10 | multiple Flutter or Dart projects. 11 | 12 | For help getting started with Flutter, view our 13 | [online documentation](https://flutter.dev/docs), which offers tutorials, 14 | samples, guidance on mobile development, and a full API reference. 15 | -------------------------------------------------------------------------------- /data/lib/data.dart: -------------------------------------------------------------------------------- 1 | library data; 2 | 3 | import 'src/di/locator.dart'; 4 | 5 | class Data { 6 | static Future init() async { 7 | await setupLocator(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /data/lib/src/data_source/local/shared_preferences_store.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:domain/domain.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | import '../../models/region_model.dart'; 7 | 8 | class SharedPrefsStore { 9 | static const String KEY_REGION = 'KEY_REGION'; 10 | final SharedPreferences _prefs; 11 | 12 | SharedPrefsStore(this._prefs); 13 | 14 | Region? getRegion() { 15 | final jsonString = _prefs.getString(KEY_REGION); 16 | if (jsonString != null) { 17 | return RegionModel.fromJson(jsonDecode(jsonString)); 18 | } 19 | } 20 | 21 | Future saveRegion(Region region) async { 22 | return await _prefs.setString(KEY_REGION, jsonEncode(region.toJson())); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /data/lib/src/data_source/remote/base/api_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | 6 | const int kApiConnectTimeout = 1000; 7 | 8 | class ApiClient { 9 | final Dio _dio; 10 | 11 | ApiClient(this._dio) { 12 | _dio.options.connectTimeout = kApiConnectTimeout; 13 | _dio.options.receiveTimeout = kApiConnectTimeout; 14 | _dio.options.sendTimeout = kApiConnectTimeout; 15 | _dio.options.contentType = Headers.jsonContentType; 16 | _dio.options.responseType = ResponseType.json; 17 | 18 | if (kDebugMode) { 19 | _dio.interceptors.add(LogInterceptor( 20 | requestBody: true, 21 | responseHeader: false, 22 | responseBody: true, 23 | )); 24 | } else { 25 | _dio.interceptors.add( 26 | InterceptorsWrapper( 27 | onResponse: (Response response, handle) { 28 | return handle.next(response); 29 | }, 30 | onError: (DioError error, handle) { 31 | if (error.response != null) { 32 | /// add log 33 | } 34 | return handle.next(error); 35 | }, 36 | ), 37 | ); 38 | } 39 | } 40 | 41 | Future> get( 42 | String uri, { 43 | Map? params, 44 | CancelToken? cancelToken, 45 | }) async { 46 | return await _dio.get( 47 | uri, 48 | queryParameters: params, 49 | cancelToken: cancelToken, 50 | ); 51 | } 52 | 53 | Future> post( 54 | String uri, { 55 | data, 56 | Map? params, 57 | CancelToken? cancelToken, 58 | }) async { 59 | return await _dio.post( 60 | uri, 61 | data: data, 62 | queryParameters: params, 63 | cancelToken: cancelToken, 64 | ); 65 | } 66 | 67 | Future> delete( 68 | String uri, { 69 | Map? params, 70 | }) async { 71 | return await _dio.delete( 72 | uri, 73 | queryParameters: params, 74 | ); 75 | } 76 | 77 | Future> put( 78 | String uri, { 79 | data, 80 | Map? params, 81 | }) async { 82 | return await _dio.put( 83 | uri, 84 | data: data, 85 | queryParameters: params, 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /data/lib/src/data_source/remote/popup_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | import 'base/api_client.dart'; 4 | 5 | abstract class PopupApi { 6 | Future getFloatPopup(); 7 | } 8 | 9 | class PopupApiImpl implements PopupApi { 10 | final ApiClient client; 11 | 12 | PopupApiImpl(this.client); 13 | 14 | @override 15 | Future getFloatPopup() { 16 | return client.get('/popup/default'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /data/lib/src/data_source/remote/regions_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | import 'base/api_client.dart'; 4 | 5 | abstract class RegionApi { 6 | Future regions(); 7 | 8 | Future regionById(int id); 9 | } 10 | 11 | class RegionApiImpl implements RegionApi { 12 | final ApiClient client; 13 | 14 | RegionApiImpl(this.client); 15 | 16 | @override 17 | Future regions() { 18 | return client.get('/regions'); 19 | } 20 | 21 | @override 22 | Future regionById(int id) { 23 | return client.get('/regions/$id'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /data/lib/src/data_source/remote/screen_setting_api.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | import 'base/api_client.dart'; 4 | 5 | abstract class ScreenSettingApi { 6 | Future getHomeSetting(); 7 | 8 | Future getShoppingSetting(); 9 | } 10 | 11 | class ScreenSettingApiImpl implements ScreenSettingApi { 12 | final ApiClient client; 13 | 14 | ScreenSettingApiImpl(this.client); 15 | 16 | @override 17 | Future getHomeSetting() { 18 | return client.get('/home'); 19 | } 20 | 21 | @override 22 | Future getShoppingSetting() { 23 | return client.get('/shopping'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /data/lib/src/data_source/service/firebase_remote_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_remote_config/firebase_remote_config.dart'; 2 | 3 | class FirebaseRemoteConfig { 4 | /// version mới nhất 5 | static const String VERSION = 'version'; 6 | 7 | /// version gần nhất bắt buộc cập nhật 8 | static const String LAST_CONSTRAINT_VERSION = 'last_constraint_version'; 9 | 10 | /// bắt buộc cập nhật nếu constraint = true 11 | static const String CONSTRAINT = 'constraint'; 12 | 13 | static const Map DEFAULT_REMOTE_CONFIG = { 14 | VERSION: '2.6.6', 15 | LAST_CONSTRAINT_VERSION: '2.6.0', 16 | CONSTRAINT: false, 17 | }; 18 | 19 | final RemoteConfig _remoteConfig; 20 | 21 | FirebaseRemoteConfig(this._remoteConfig); 22 | 23 | Future getConfig() async { 24 | await _remoteConfig.setDefaults(DEFAULT_REMOTE_CONFIG); 25 | await _remoteConfig.setConfigSettings(RemoteConfigSettings( 26 | fetchTimeout: const Duration(seconds: 30), 27 | minimumFetchInterval: const Duration(hours: 1), 28 | )); 29 | await _remoteConfig.fetchAndActivate(); 30 | return _remoteConfig; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /data/lib/src/di/locator.dart: -------------------------------------------------------------------------------- 1 | import 'package:core/core.dart'; 2 | import 'package:dio/dio.dart'; 3 | import 'package:domain/domain.dart'; 4 | import 'package:get_it/get_it.dart'; 5 | import 'package:package_info/package_info.dart'; 6 | import 'package:shared_preferences/shared_preferences.dart'; 7 | 8 | import '../data_source/local/shared_preferences_store.dart'; 9 | import '../data_source/remote/base/api_client.dart'; 10 | import '../data_source/remote/popup_api.dart'; 11 | import '../data_source/remote/regions_api.dart'; 12 | import '../data_source/remote/screen_setting_api.dart'; 13 | import '../repository/api_error_handle_repo_impl.dart'; 14 | import '../repository/internet_status_repo_impl.dart'; 15 | import '../repository/popup_repo_impl.dart'; 16 | import '../repository/region_repo_impl.dart'; 17 | import '../repository/screen_setting_repo_impl.dart'; 18 | 19 | final locator = GetIt.instance..allowReassignment = true; 20 | 21 | Future setupLocator() async { 22 | await _registerExternal(); 23 | await _registerApiServices(); 24 | _registerNetworkModules(); 25 | _registerRepository(); 26 | } 27 | 28 | Future _registerRepository() async { 29 | locator.registerLazySingleton(() => InternetStatusRepoImpl()); 30 | locator.registerLazySingleton(() => ApiErrorHandleRepoImpl(locator())); 31 | 32 | locator.registerFactory(() => ScreenSettingRepoImpl(locator())); 33 | locator.registerLazySingleton(() => RegionRepoImpl(locator(), locator())); 34 | locator.registerFactory(() => PopupRepoImpl(locator())); 35 | } 36 | 37 | Future _registerApiServices() async { 38 | locator.registerLazySingleton(() => ScreenSettingApiImpl(locator())); 39 | locator.registerFactory(() => RegionApiImpl(locator())); 40 | locator.registerFactory(() => PopupApiImpl(locator())); 41 | } 42 | 43 | void _registerNetworkModules() { 44 | locator.registerLazySingleton(() => ApiClient(locator())); 45 | } 46 | 47 | Future _registerExternal() async { 48 | final prefs = await SharedPreferences.getInstance(); 49 | locator.registerSingleton(prefs); 50 | locator.registerSingleton(Dio(BaseOptions(baseUrl: locator().baseUrl))); 51 | locator.registerSingleton(SharedPrefsStore(locator())); 52 | final packageInfo = await PackageInfo.fromPlatform(); 53 | locator.registerSingleton(packageInfo); 54 | } 55 | -------------------------------------------------------------------------------- /data/lib/src/models/list_response_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:utils/utils.dart'; 3 | 4 | import 'metadata_model.dart'; 5 | 6 | class ListResponseModel extends ListResponse { 7 | ListResponseModel({ 8 | required Metadata metadata, 9 | required List data, 10 | }) : super( 11 | metadata: metadata, 12 | data: data, 13 | ); 14 | 15 | factory ListResponseModel.fromJson( 16 | String key, 17 | Map json, 18 | T Function(Map) factoryFunction, 19 | ) { 20 | final metadataJson = Utils.parseJson('metadata', json); 21 | final dataList = Utils.parseJsonToList(key, json, factoryFunction); 22 | return ListResponseModel( 23 | metadata: MetadataModel.fromJson(metadataJson), 24 | data: dataList, 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /data/lib/src/models/metadata_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:utils/utils.dart'; 3 | 4 | class MetadataModel extends Metadata { 5 | MetadataModel({ 6 | int total = 0, 7 | int page = 0, 8 | int limit = 0, 9 | }) : super( 10 | total: total, 11 | page: page, 12 | limit: limit, 13 | ); 14 | 15 | factory MetadataModel.fromJson(Map json) { 16 | return MetadataModel( 17 | total: Utils.parseJsonToInt('total', json)!, 18 | page: Utils.parseJsonToInt('page', json)!, 19 | limit: Utils.parseJsonToInt('limit', json)!, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /data/lib/src/models/popup_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:utils/utils.dart'; 3 | 4 | class PopupModel extends Popup { 5 | PopupModel({ 6 | required int id, 7 | String? name, 8 | String? imageUrl, 9 | String? deepLink, 10 | int? screenId, 11 | String? status, 12 | }) : super( 13 | id: id, 14 | name: name, 15 | imageUrl: imageUrl, 16 | deepLink: deepLink, 17 | screenId: screenId, 18 | status: status, 19 | ); 20 | 21 | factory PopupModel.fromJson(Map json) { 22 | return PopupModel( 23 | id: Utils.parseJsonToInt('id', json)!, 24 | name: Utils.parseJson('name', json), 25 | imageUrl: Utils.parseJson('image_url', json), 26 | deepLink: Utils.parseJson('deep_link', json), 27 | screenId: Utils.parseJsonToInt('screen_id', json), 28 | status: Utils.parseJson('status', json), 29 | ); 30 | } 31 | 32 | static List fromList(List json) { 33 | return Utils.listFromJson( 34 | json, 35 | (item) => PopupModel.fromJson(item), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /data/lib/src/models/region_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:utils/utils.dart'; 3 | 4 | class RegionModel extends Region { 5 | const RegionModel({ 6 | required int id, 7 | required String name, 8 | }) : super( 9 | id: id, 10 | name: name, 11 | ); 12 | 13 | factory RegionModel.fromJson(Map json) { 14 | return RegionModel( 15 | id: Utils.parseJsonToInt('id', json)!, 16 | name: Utils.parseJson('name', json)!, 17 | ); 18 | } 19 | 20 | static List fromList(List json) { 21 | return Utils.listFromJson( 22 | json, 23 | (item) => RegionModel.fromJson(item), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /data/lib/src/models/screen_setting_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:utils/utils.dart'; 3 | 4 | class ScreenSettingModel extends ScreenSetting { 5 | ScreenSettingModel({ 6 | final String? backgroundUrl, 7 | final String? color, 8 | final String? footBackgroundUrl, 9 | final String? noticeDeliveryEpidemic, 10 | final String? text, 11 | }) : super( 12 | backgroundUrl: backgroundUrl, 13 | color: color, 14 | footBackgroundUrl: footBackgroundUrl, 15 | text: text, 16 | ); 17 | 18 | factory ScreenSettingModel.fromJson(Map json) { 19 | return ScreenSettingModel( 20 | backgroundUrl: Utils.parseJson('background_url', json), 21 | color: Utils.parseJson('color', json), 22 | footBackgroundUrl: Utils.parseJson('foot_background_url', json), 23 | text: Utils.parseJson('text', json), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /data/lib/src/repository/api_error_handle_repo_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:domain/domain.dart'; 3 | 4 | class ApiErrorHandleRepoImpl implements ApiErrorHandleRepo { 5 | final Dio _dio; 6 | 7 | ApiErrorHandleRepoImpl(this._dio); 8 | 9 | @override 10 | void getError(interceptor) { 11 | _dio.interceptors.add(interceptor); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /data/lib/src/repository/base/base_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:domain/domain.dart'; 3 | 4 | typedef ResponseToModel = Model Function(dynamic); 5 | 6 | abstract class BaseRepository { 7 | Future> safeApiCall( 8 | Future call, { 9 | required ResponseToModel mapper, 10 | }) async { 11 | try { 12 | var response = await call; 13 | return Success(mapper.call(response.data)); 14 | } on Exception catch (exception) { 15 | if (exception is DioError) { 16 | switch (exception.type) { 17 | case DioErrorType.connectTimeout: 18 | case DioErrorType.sendTimeout: 19 | case DioErrorType.receiveTimeout: 20 | case DioErrorType.cancel: 21 | return Error(ErrorType.POOR_NETWORK, 'Hãy kiểm tra kết nối mạng của bạn!'); 22 | 23 | case DioErrorType.other: 24 | return Error(ErrorType.NO_NETWORK, 'Không có kết nối Internet!'); 25 | 26 | case DioErrorType.response: 27 | return ServerError( 28 | type: ErrorType.SERVER, 29 | error: exception.message, 30 | code: exception.response!.statusCode!, 31 | ); 32 | } 33 | } 34 | return Error(ErrorType.GENERIC, "Xảy ra lỗi, hãy thử lại!"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /data/lib/src/repository/internet_status_repo_impl.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:domain/domain.dart'; 4 | import 'package:internet_connection_checker/internet_connection_checker.dart'; 5 | 6 | class InternetStatusRepoImpl implements InternetStatusRepo { 7 | final _connectivity = InternetConnectionChecker(); 8 | 9 | @override 10 | Stream onChange() => _connectivity.onStatusChange.transform( 11 | StreamTransformer.fromHandlers(handleData: (status, sink) { 12 | if (status == InternetConnectionStatus.connected) { 13 | sink.add(true); 14 | } 15 | sink.add(false); 16 | }), 17 | ); 18 | 19 | @override 20 | Future get isConnected async => await _connectivity.hasConnection; 21 | } 22 | -------------------------------------------------------------------------------- /data/lib/src/repository/popup_repo_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | 3 | import '../data_source/remote/popup_api.dart'; 4 | import '../models/popup_model.dart'; 5 | import 'base/base_repository.dart'; 6 | 7 | class PopupRepoImpl extends BaseRepository implements PopupRepo { 8 | final PopupApi _popupApi; 9 | 10 | PopupRepoImpl(this._popupApi); 11 | 12 | @override 13 | Future> getFloatPopup() { 14 | return safeApiCall( 15 | _popupApi.getFloatPopup(), 16 | mapper: (data) => PopupModel.fromJson(data), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /data/lib/src/repository/region_repo_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | 3 | import '../data_source/local/shared_preferences_store.dart'; 4 | import '../data_source/remote/regions_api.dart'; 5 | import '../models/region_model.dart'; 6 | import 'base/base_repository.dart'; 7 | 8 | class RegionRepoImpl extends BaseRepository implements RegionRepo { 9 | final RegionApi _regionApi; 10 | final SharedPrefsStore _prefs; 11 | 12 | List _defaultRegion = Region.defaultRegions; 13 | 14 | Region? _region = null; 15 | 16 | RegionRepoImpl(this._regionApi, this._prefs); 17 | 18 | @override 19 | Region? get region => _region ?? _prefs.getRegion(); 20 | 21 | @override 22 | Future>> getRegions() async { 23 | final result = await safeApiCall>( 24 | _regionApi.regions(), 25 | mapper: (data) => RegionModel.fromList(data), 26 | ); 27 | if (result.isSuccessful) { 28 | _defaultRegion = (result as Success>).data; 29 | } 30 | return result; 31 | } 32 | 33 | @override 34 | List getRegionsLocal() => _defaultRegion; 35 | 36 | @override 37 | Future updateRegion(Region region) async { 38 | _prefs.saveRegion(region); 39 | _region = region; 40 | return true; 41 | } 42 | 43 | @override 44 | Future> getRegionById(int id) { 45 | return safeApiCall( 46 | _regionApi.regionById(id), 47 | mapper: (data) => RegionModel.fromJson(data), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /data/lib/src/repository/screen_setting_repo_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | 3 | import '../data_source/remote/screen_setting_api.dart'; 4 | import '../models/screen_setting_model.dart'; 5 | import 'base/base_repository.dart'; 6 | 7 | class ScreenSettingRepoImpl extends BaseRepository implements ScreenSettingRepo { 8 | final ScreenSettingApi _remoteApi; 9 | 10 | ScreenSettingRepoImpl(this._remoteApi); 11 | 12 | @override 13 | Future> getHomeSetting() { 14 | return safeApiCall( 15 | _remoteApi.getHomeSetting(), 16 | mapper: (data) => ScreenSettingModel.fromJson(data), 17 | ); 18 | } 19 | 20 | @override 21 | Future> getShoppingSetting() { 22 | return safeApiCall( 23 | _remoteApi.getShoppingSetting(), 24 | mapper: (data) => ScreenSettingModel.fromJson(data), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /data/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: data 2 | description: A new Flutter project. 3 | version: 0.0.1 4 | publish_to: none 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | core: 15 | path: ../core 16 | domain: 17 | path: ../domain 18 | 19 | utils: 20 | path: ../plugin/utils 21 | 22 | # inversion of control 23 | get_it: 7.2.0 24 | # network 25 | dio: 4.0.0 26 | shared_preferences: 2.0.8 27 | internet_connection_checker: 0.0.1+2 28 | package_info: 2.0.2 29 | 30 | dev_dependencies: 31 | flutter_test: 32 | sdk: flutter 33 | 34 | # flutter packages pub run build_runner watch 35 | build_runner: 36 | mockito: ^5.0.16 37 | 38 | # The following section is specific to Flutter. 39 | flutter: 40 | 41 | -------------------------------------------------------------------------------- /data/test/data_source/remote/screen_setting_api_test.dart: -------------------------------------------------------------------------------- 1 | // import 'package:data/src/data_source/remote/base/api_client.dart'; 2 | // import 'package:dio/dio.dart'; 3 | // import 'package:flutter_test/flutter_test.dart'; 4 | 5 | void main() { 6 | // late Dio dio; 7 | // late LogApiService log; 8 | // late ApiClient client; 9 | // 10 | // setUp(() { 11 | // dio = Dio(BaseOptions(baseUrl: baseUrl)); 12 | // log = LogApiService(baseUrl); 13 | // client = ApiClient(dio, log); 14 | // }); 15 | } 16 | -------------------------------------------------------------------------------- /data/test/data_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | test('adds one to input values', () {}); 5 | } 6 | -------------------------------------------------------------------------------- /data/test/repository/internet_status_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:data/src/repository/internet_status_repo_impl.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | late InternetStatusRepoImpl internetStatus; 6 | 7 | setUp(() { 8 | internetStatus = InternetStatusRepoImpl(); 9 | }); 10 | 11 | test('Un awaited isConnected', () async { 12 | final result = internetStatus.isConnected; 13 | expect(result, isA>()); 14 | }); 15 | 16 | test('Awaited isConnected', () async { 17 | final result = await internetStatus.isConnected; 18 | expect(result, isA()); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /domain/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/ephemeral 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | 71 | # Exceptions to above rules. 72 | !**/ios/**/default.mode1v3 73 | !**/ios/**/default.mode2v3 74 | !**/ios/**/default.pbxuser 75 | !**/ios/**/default.perspectivev3 76 | /coverage/ 77 | -------------------------------------------------------------------------------- /domain/.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: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /domain/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /domain/README.md: -------------------------------------------------------------------------------- 1 | # domain 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Dart 8 | [package](https://flutter.dev/developing-packages/), 9 | a library module containing code that can be shared easily across 10 | multiple Flutter or Dart projects. 11 | 12 | For help getting started with Flutter, view our 13 | [online documentation](https://flutter.dev/docs), which offers tutorials, 14 | samples, guidance on mobile development, and a full API reference. 15 | -------------------------------------------------------------------------------- /domain/lib/domain.dart: -------------------------------------------------------------------------------- 1 | library domain; 2 | 3 | import 'src/di/locator.dart'; 4 | 5 | export 'src/common/error_type.dart'; 6 | export 'src/common/result.dart'; 7 | export 'src/entities/list_response.dart'; 8 | export 'src/entities/metadata.dart'; 9 | export 'src/entities/popup.dart'; 10 | export 'src/entities/region.dart'; 11 | export 'src/entities/screen_setting.dart'; 12 | export 'src/entities/validator.dart'; 13 | export 'src/repositories/api_error_handler_repo.dart'; 14 | export 'src/repositories/internet_status_repo.dart'; 15 | export 'src/repositories/popup_repo.dart'; 16 | export 'src/repositories/region_repo.dart'; 17 | export 'src/repositories/screen_setting_repo.dart'; 18 | export 'src/usecase/api_error_handler.dart'; 19 | export 'src/usecase/base/use_case.dart'; 20 | export 'src/usecase/check_internet_status.dart'; 21 | export 'src/usecase/get_float_popup.dart'; 22 | export 'src/usecase/get_home_screen_setting.dart'; 23 | export 'src/usecase/get_region.dart'; 24 | export 'src/usecase/get_regions.dart'; 25 | export 'src/usecase/get_regions_local.dart'; 26 | export 'src/usecase/get_shopping_screen_setting.dart'; 27 | export 'src/usecase/update_region.dart'; 28 | 29 | class Domain { 30 | static Future init() async { 31 | setupLocator(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /domain/lib/src/common/error_type.dart: -------------------------------------------------------------------------------- 1 | enum ErrorType { POOR_NETWORK, NO_NETWORK, SERVER, GENERIC } 2 | -------------------------------------------------------------------------------- /domain/lib/src/common/result.dart: -------------------------------------------------------------------------------- 1 | import 'error_type.dart'; 2 | 3 | class Result with SealedResult { 4 | bool get isSuccessful => this is Success; 5 | 6 | Result transform({ 7 | required T Function(T)? success, 8 | Error Function(Error)? error, 9 | }) { 10 | if (this is Success && success != null) { 11 | (this as Success).data = success.call((this as Success).data); 12 | } 13 | if (this is Error && error != null) { 14 | return error.call(this as Error); 15 | } 16 | return this; 17 | } 18 | } 19 | 20 | class Success extends Result { 21 | T data; 22 | 23 | Success(this.data); 24 | } 25 | 26 | class Error extends Result { 27 | ErrorType type; 28 | String message; 29 | 30 | Error(this.type, this.message); 31 | } 32 | 33 | class ServerError extends Error { 34 | int code; 35 | 36 | ServerError({ 37 | required ErrorType type, 38 | required String error, 39 | required this.code, 40 | }) : super(type, error); 41 | } 42 | 43 | abstract class SealedResult { 44 | R? when({ 45 | R Function(T)? success, 46 | R Function(Error)? error, 47 | }) { 48 | if (this is Success) { 49 | return success?.call((this as Success).data); 50 | } 51 | if (this is Error) { 52 | return error?.call(this as Error); 53 | } 54 | throw new Exception('If you got here, probably you forgot to regenerate the classes? ' 55 | 'Try running flutter packages pub run build_runner build'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /domain/lib/src/di/locator.dart: -------------------------------------------------------------------------------- 1 | import 'package:get_it/get_it.dart'; 2 | 3 | import '../../domain.dart'; 4 | 5 | final locator = GetIt.instance..allowReassignment = true; 6 | 7 | void setupLocator() { 8 | /// Use case 9 | locator.registerFactory(() => CheckInternetStatus(locator())); 10 | locator.registerFactory(() => ApiErrorHandle(locator())); 11 | locator.registerFactory(() => GetRegions(locator())); 12 | locator.registerFactory(() => GetRegionsLocal(locator())); 13 | locator.registerFactory(() => GetRegion(locator())); 14 | locator.registerFactory(() => UpdateRegion(locator())); 15 | locator.registerFactory(() => GetHomeScreenSetting(locator())); 16 | locator.registerFactory(() => GetShoppingScreenSetting(locator())); 17 | locator.registerFactory(() => GetFloatPopup(locator())); 18 | } 19 | -------------------------------------------------------------------------------- /domain/lib/src/entities/list_response.dart: -------------------------------------------------------------------------------- 1 | import 'metadata.dart'; 2 | 3 | class ListResponse { 4 | final Metadata metadata; 5 | final List data; 6 | 7 | ListResponse({ 8 | required this.metadata, 9 | required this.data, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /domain/lib/src/entities/metadata.dart: -------------------------------------------------------------------------------- 1 | class Metadata { 2 | final int total; 3 | final int page; 4 | final int limit; 5 | 6 | const Metadata({ 7 | this.total = 0, 8 | this.page = 0, 9 | this.limit = 0, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /domain/lib/src/entities/popup.dart: -------------------------------------------------------------------------------- 1 | class Popup { 2 | final int id; 3 | final String? name; 4 | final String? imageUrl; 5 | final String? deepLink; 6 | final int? screenId; 7 | final String? status; 8 | 9 | Popup({ 10 | required this.id, 11 | this.name, 12 | this.imageUrl, 13 | this.deepLink, 14 | this.screenId, 15 | this.status, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /domain/lib/src/entities/region.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class Region extends Equatable { 4 | final int id; 5 | final String name; 6 | 7 | const Region({ 8 | required this.id, 9 | required this.name, 10 | }); 11 | 12 | static const int idNorthern = 1; 13 | static const int idSouthern = 2; 14 | 15 | static List defaultRegions = [ 16 | const Region( 17 | id: idNorthern, 18 | name: 'Miền Bắc', 19 | ), 20 | const Region( 21 | id: idSouthern, 22 | name: 'Miền Nam', 23 | ), 24 | ]; 25 | 26 | Map toJson() { 27 | return { 28 | 'id': id, 29 | 'name': name, 30 | }; 31 | } 32 | 33 | @override 34 | List get props => [ 35 | id, 36 | name, 37 | ]; 38 | } 39 | -------------------------------------------------------------------------------- /domain/lib/src/entities/screen_setting.dart: -------------------------------------------------------------------------------- 1 | class ScreenSetting { 2 | final String? backgroundUrl; 3 | final String? color; 4 | final String? footBackgroundUrl; 5 | final String? text; 6 | 7 | const ScreenSetting({ 8 | this.backgroundUrl, 9 | this.color, 10 | this.footBackgroundUrl, 11 | this.text, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /domain/lib/src/entities/validator.dart: -------------------------------------------------------------------------------- 1 | class Validator { 2 | // chỉ số nguyên dương 3 | static bool validateNumber(String text) { 4 | Pattern pattern = r'^(\(?\+?[0-9]*\)?)?[0-9(\)]*$'; 5 | RegExp regex = new RegExp(pattern.toString()); 6 | return regex.hasMatch(text); 7 | } 8 | 9 | // chỉ chữ cái không dấu và số nguyên dương 10 | static bool validateNumberAndText(String text) { 11 | Pattern pattern = r"^[0-9a-z]"; 12 | RegExp regex = new RegExp(pattern.toString()); 13 | return regex.hasMatch(text); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/lib/src/repositories/api_error_handler_repo.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | abstract class ApiErrorHandleRepo { 4 | void getError(Interceptor interceptor); 5 | } 6 | -------------------------------------------------------------------------------- /domain/lib/src/repositories/internet_status_repo.dart: -------------------------------------------------------------------------------- 1 | abstract class InternetStatusRepo { 2 | Future get isConnected; 3 | Stream onChange(); 4 | } 5 | -------------------------------------------------------------------------------- /domain/lib/src/repositories/popup_repo.dart: -------------------------------------------------------------------------------- 1 | import '../../domain.dart'; 2 | 3 | abstract class PopupRepo { 4 | Future> getFloatPopup(); 5 | } 6 | -------------------------------------------------------------------------------- /domain/lib/src/repositories/region_repo.dart: -------------------------------------------------------------------------------- 1 | import '../common/result.dart'; 2 | import '../entities/region.dart'; 3 | 4 | abstract class RegionRepo { 5 | Region? get region; 6 | 7 | Future>> getRegions(); 8 | 9 | List getRegionsLocal(); 10 | 11 | Future> getRegionById(int id); 12 | 13 | Future updateRegion(Region region); 14 | } 15 | -------------------------------------------------------------------------------- /domain/lib/src/repositories/screen_setting_repo.dart: -------------------------------------------------------------------------------- 1 | import '../common/result.dart'; 2 | import '../entities/screen_setting.dart'; 3 | 4 | abstract class ScreenSettingRepo { 5 | Future> getHomeSetting(); 6 | 7 | Future> getShoppingSetting(); 8 | } 9 | -------------------------------------------------------------------------------- /domain/lib/src/usecase/api_error_handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | import '../repositories/api_error_handler_repo.dart'; 4 | 5 | class ApiErrorHandle { 6 | final ApiErrorHandleRepo _repo; 7 | 8 | ApiErrorHandle(this._repo); 9 | 10 | void call(Interceptor interceptor) { 11 | _repo.getError(interceptor); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /domain/lib/src/usecase/base/use_case.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | import '../../common/result.dart'; 4 | 5 | abstract class UseCase { 6 | call(); 7 | } 8 | 9 | abstract class UseCaseResult extends UseCase { 10 | Future> call({Params params}); 11 | } 12 | 13 | class NoParams extends Equatable { 14 | @override 15 | List get props => []; 16 | } 17 | -------------------------------------------------------------------------------- /domain/lib/src/usecase/check_internet_status.dart: -------------------------------------------------------------------------------- 1 | import '../repositories/internet_status_repo.dart'; 2 | import 'base/use_case.dart'; 3 | 4 | class CheckInternetStatus implements UseCase { 5 | final InternetStatusRepo _repo; 6 | 7 | CheckInternetStatus(this._repo); 8 | 9 | @override 10 | Stream call() => _repo.onChange(); 11 | } 12 | -------------------------------------------------------------------------------- /domain/lib/src/usecase/get_float_popup.dart: -------------------------------------------------------------------------------- 1 | import '../../domain.dart'; 2 | 3 | class GetFloatPopup extends UseCaseResult { 4 | final PopupRepo _repo; 5 | 6 | GetFloatPopup(this._repo); 7 | 8 | @override 9 | Future> call({NoParams? params}) => _repo.getFloatPopup(); 10 | } 11 | -------------------------------------------------------------------------------- /domain/lib/src/usecase/get_home_screen_setting.dart: -------------------------------------------------------------------------------- 1 | import '../common/result.dart'; 2 | import '../entities/screen_setting.dart'; 3 | import '../repositories/screen_setting_repo.dart'; 4 | import 'base/use_case.dart'; 5 | 6 | class GetHomeScreenSetting implements UseCaseResult { 7 | final ScreenSettingRepo _repo; 8 | 9 | GetHomeScreenSetting(this._repo); 10 | 11 | @override 12 | Future> call({NoParams? params}) async { 13 | return await _repo.getHomeSetting(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/lib/src/usecase/get_region.dart: -------------------------------------------------------------------------------- 1 | import '../entities/region.dart'; 2 | import '../repositories/region_repo.dart'; 3 | import 'base/use_case.dart'; 4 | 5 | class GetRegion implements UseCase { 6 | final RegionRepo _regionRepo; 7 | 8 | const GetRegion(this._regionRepo); 9 | 10 | @override 11 | Region? call() => _regionRepo.region; 12 | } 13 | -------------------------------------------------------------------------------- /domain/lib/src/usecase/get_regions.dart: -------------------------------------------------------------------------------- 1 | import '../common/result.dart'; 2 | import '../entities/region.dart'; 3 | import '../repositories/region_repo.dart'; 4 | import 'base/use_case.dart'; 5 | 6 | class GetRegions implements UseCaseResult, NoParams> { 7 | final RegionRepo _regionRepo; 8 | 9 | const GetRegions(this._regionRepo); 10 | 11 | @override 12 | Future>> call({NoParams? params}) async { 13 | return await _regionRepo.getRegions(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/lib/src/usecase/get_regions_local.dart: -------------------------------------------------------------------------------- 1 | import '../entities/region.dart'; 2 | import '../repositories/region_repo.dart'; 3 | import 'base/use_case.dart'; 4 | 5 | class GetRegionsLocal implements UseCase { 6 | final RegionRepo _regionRepo; 7 | 8 | const GetRegionsLocal(this._regionRepo); 9 | 10 | @override 11 | List call() => _regionRepo.getRegionsLocal(); 12 | } 13 | -------------------------------------------------------------------------------- /domain/lib/src/usecase/get_shopping_screen_setting.dart: -------------------------------------------------------------------------------- 1 | import '../common/result.dart'; 2 | import '../entities/screen_setting.dart'; 3 | import '../repositories/screen_setting_repo.dart'; 4 | import 'base/use_case.dart'; 5 | 6 | class GetShoppingScreenSetting implements UseCaseResult { 7 | final ScreenSettingRepo _repo; 8 | 9 | GetShoppingScreenSetting(this._repo); 10 | 11 | @override 12 | Future> call({NoParams? params}) async { 13 | return await _repo.getShoppingSetting(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /domain/lib/src/usecase/update_region.dart: -------------------------------------------------------------------------------- 1 | import '../entities/region.dart'; 2 | import '../repositories/region_repo.dart'; 3 | 4 | class UpdateRegion { 5 | final RegionRepo _regionRepo; 6 | 7 | const UpdateRegion(this._regionRepo); 8 | 9 | Future call(Region region) async { 10 | return await _regionRepo.updateRegion(region); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /domain/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "25.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.2.0" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.2.0" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.8.1" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.1.0" 39 | build: 40 | dependency: transitive 41 | description: 42 | name: build 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "2.1.0" 46 | build_config: 47 | dependency: transitive 48 | description: 49 | name: build_config 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.0.0" 53 | build_daemon: 54 | dependency: transitive 55 | description: 56 | name: build_daemon 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "3.0.0" 60 | build_resolvers: 61 | dependency: transitive 62 | description: 63 | name: build_resolvers 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.0.4" 67 | build_runner: 68 | dependency: "direct dev" 69 | description: 70 | name: build_runner 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "2.1.2" 74 | build_runner_core: 75 | dependency: transitive 76 | description: 77 | name: build_runner_core 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "7.1.0" 81 | built_collection: 82 | dependency: transitive 83 | description: 84 | name: built_collection 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "5.1.1" 88 | built_value: 89 | dependency: transitive 90 | description: 91 | name: built_value 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "8.1.2" 95 | characters: 96 | dependency: transitive 97 | description: 98 | name: characters 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.1.0" 102 | charcode: 103 | dependency: transitive 104 | description: 105 | name: charcode 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "1.3.1" 109 | checked_yaml: 110 | dependency: transitive 111 | description: 112 | name: checked_yaml 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "2.0.1" 116 | cli_util: 117 | dependency: transitive 118 | description: 119 | name: cli_util 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "0.3.3" 123 | clock: 124 | dependency: transitive 125 | description: 126 | name: clock 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.1.0" 130 | code_builder: 131 | dependency: transitive 132 | description: 133 | name: code_builder 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "4.1.0" 137 | collection: 138 | dependency: transitive 139 | description: 140 | name: collection 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "1.15.0" 144 | convert: 145 | dependency: transitive 146 | description: 147 | name: convert 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "3.0.1" 151 | crypto: 152 | dependency: transitive 153 | description: 154 | name: crypto 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "3.0.1" 158 | dart_style: 159 | dependency: transitive 160 | description: 161 | name: dart_style 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "2.0.3" 165 | dio: 166 | dependency: "direct main" 167 | description: 168 | name: dio 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "4.0.0" 172 | equatable: 173 | dependency: "direct main" 174 | description: 175 | name: equatable 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "2.0.3" 179 | fake_async: 180 | dependency: transitive 181 | description: 182 | name: fake_async 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "1.2.0" 186 | file: 187 | dependency: transitive 188 | description: 189 | name: file 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "6.1.2" 193 | fixnum: 194 | dependency: transitive 195 | description: 196 | name: fixnum 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "1.0.0" 200 | flutter: 201 | dependency: "direct main" 202 | description: flutter 203 | source: sdk 204 | version: "0.0.0" 205 | flutter_test: 206 | dependency: "direct dev" 207 | description: flutter 208 | source: sdk 209 | version: "0.0.0" 210 | frontend_server_client: 211 | dependency: transitive 212 | description: 213 | name: frontend_server_client 214 | url: "https://pub.dartlang.org" 215 | source: hosted 216 | version: "2.1.2" 217 | get_it: 218 | dependency: "direct main" 219 | description: 220 | name: get_it 221 | url: "https://pub.dartlang.org" 222 | source: hosted 223 | version: "7.2.0" 224 | glob: 225 | dependency: transitive 226 | description: 227 | name: glob 228 | url: "https://pub.dartlang.org" 229 | source: hosted 230 | version: "2.0.1" 231 | graphs: 232 | dependency: transitive 233 | description: 234 | name: graphs 235 | url: "https://pub.dartlang.org" 236 | source: hosted 237 | version: "2.0.0" 238 | http_multi_server: 239 | dependency: transitive 240 | description: 241 | name: http_multi_server 242 | url: "https://pub.dartlang.org" 243 | source: hosted 244 | version: "3.0.1" 245 | http_parser: 246 | dependency: transitive 247 | description: 248 | name: http_parser 249 | url: "https://pub.dartlang.org" 250 | source: hosted 251 | version: "4.0.0" 252 | io: 253 | dependency: transitive 254 | description: 255 | name: io 256 | url: "https://pub.dartlang.org" 257 | source: hosted 258 | version: "1.0.3" 259 | js: 260 | dependency: transitive 261 | description: 262 | name: js 263 | url: "https://pub.dartlang.org" 264 | source: hosted 265 | version: "0.6.3" 266 | json_annotation: 267 | dependency: transitive 268 | description: 269 | name: json_annotation 270 | url: "https://pub.dartlang.org" 271 | source: hosted 272 | version: "4.0.1" 273 | logging: 274 | dependency: transitive 275 | description: 276 | name: logging 277 | url: "https://pub.dartlang.org" 278 | source: hosted 279 | version: "1.0.1" 280 | matcher: 281 | dependency: transitive 282 | description: 283 | name: matcher 284 | url: "https://pub.dartlang.org" 285 | source: hosted 286 | version: "0.12.10" 287 | meta: 288 | dependency: transitive 289 | description: 290 | name: meta 291 | url: "https://pub.dartlang.org" 292 | source: hosted 293 | version: "1.7.0" 294 | mime: 295 | dependency: transitive 296 | description: 297 | name: mime 298 | url: "https://pub.dartlang.org" 299 | source: hosted 300 | version: "1.0.0" 301 | package_config: 302 | dependency: transitive 303 | description: 304 | name: package_config 305 | url: "https://pub.dartlang.org" 306 | source: hosted 307 | version: "2.0.0" 308 | path: 309 | dependency: transitive 310 | description: 311 | name: path 312 | url: "https://pub.dartlang.org" 313 | source: hosted 314 | version: "1.8.0" 315 | pedantic: 316 | dependency: transitive 317 | description: 318 | name: pedantic 319 | url: "https://pub.dartlang.org" 320 | source: hosted 321 | version: "1.11.1" 322 | pool: 323 | dependency: transitive 324 | description: 325 | name: pool 326 | url: "https://pub.dartlang.org" 327 | source: hosted 328 | version: "1.5.0" 329 | pub_semver: 330 | dependency: transitive 331 | description: 332 | name: pub_semver 333 | url: "https://pub.dartlang.org" 334 | source: hosted 335 | version: "2.0.0" 336 | pubspec_parse: 337 | dependency: transitive 338 | description: 339 | name: pubspec_parse 340 | url: "https://pub.dartlang.org" 341 | source: hosted 342 | version: "1.0.0" 343 | shelf: 344 | dependency: transitive 345 | description: 346 | name: shelf 347 | url: "https://pub.dartlang.org" 348 | source: hosted 349 | version: "1.2.0" 350 | shelf_web_socket: 351 | dependency: transitive 352 | description: 353 | name: shelf_web_socket 354 | url: "https://pub.dartlang.org" 355 | source: hosted 356 | version: "1.0.1" 357 | sky_engine: 358 | dependency: transitive 359 | description: flutter 360 | source: sdk 361 | version: "0.0.99" 362 | source_span: 363 | dependency: transitive 364 | description: 365 | name: source_span 366 | url: "https://pub.dartlang.org" 367 | source: hosted 368 | version: "1.8.1" 369 | stack_trace: 370 | dependency: transitive 371 | description: 372 | name: stack_trace 373 | url: "https://pub.dartlang.org" 374 | source: hosted 375 | version: "1.10.0" 376 | stream_channel: 377 | dependency: transitive 378 | description: 379 | name: stream_channel 380 | url: "https://pub.dartlang.org" 381 | source: hosted 382 | version: "2.1.0" 383 | stream_transform: 384 | dependency: transitive 385 | description: 386 | name: stream_transform 387 | url: "https://pub.dartlang.org" 388 | source: hosted 389 | version: "2.0.0" 390 | string_scanner: 391 | dependency: transitive 392 | description: 393 | name: string_scanner 394 | url: "https://pub.dartlang.org" 395 | source: hosted 396 | version: "1.1.0" 397 | term_glyph: 398 | dependency: transitive 399 | description: 400 | name: term_glyph 401 | url: "https://pub.dartlang.org" 402 | source: hosted 403 | version: "1.2.0" 404 | test_api: 405 | dependency: transitive 406 | description: 407 | name: test_api 408 | url: "https://pub.dartlang.org" 409 | source: hosted 410 | version: "0.4.2" 411 | timing: 412 | dependency: transitive 413 | description: 414 | name: timing 415 | url: "https://pub.dartlang.org" 416 | source: hosted 417 | version: "1.0.0" 418 | typed_data: 419 | dependency: transitive 420 | description: 421 | name: typed_data 422 | url: "https://pub.dartlang.org" 423 | source: hosted 424 | version: "1.3.0" 425 | vector_math: 426 | dependency: transitive 427 | description: 428 | name: vector_math 429 | url: "https://pub.dartlang.org" 430 | source: hosted 431 | version: "2.1.0" 432 | watcher: 433 | dependency: transitive 434 | description: 435 | name: watcher 436 | url: "https://pub.dartlang.org" 437 | source: hosted 438 | version: "1.0.0" 439 | web_socket_channel: 440 | dependency: transitive 441 | description: 442 | name: web_socket_channel 443 | url: "https://pub.dartlang.org" 444 | source: hosted 445 | version: "2.1.0" 446 | yaml: 447 | dependency: transitive 448 | description: 449 | name: yaml 450 | url: "https://pub.dartlang.org" 451 | source: hosted 452 | version: "3.1.0" 453 | sdks: 454 | dart: ">=2.12.0 <3.0.0" 455 | flutter: ">=1.17.0" 456 | -------------------------------------------------------------------------------- /domain/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: domain 2 | description: A new Flutter project. 3 | version: 0.0.1 4 | publish_to: none 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | get_it: 7.2.0 15 | equatable: 2.0.3 16 | 17 | dio: 4.0.0 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | 23 | # flutter packages pub run build_runner watch 24 | build_runner: 25 | 26 | # The following section is specific to Flutter. 27 | flutter: 28 | -------------------------------------------------------------------------------- /domain/test/domain_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | test('adds one to input values', () {}); 5 | } 6 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /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 | - FMDB (2.7.5): 4 | - FMDB/standard (= 2.7.5) 5 | - FMDB/standard (2.7.5) 6 | - package_info (0.0.1): 7 | - Flutter 8 | - path_provider (0.0.1): 9 | - Flutter 10 | - shared_preferences (0.0.1): 11 | - Flutter 12 | - sqflite (0.0.2): 13 | - Flutter 14 | - FMDB (>= 2.7.5) 15 | 16 | DEPENDENCIES: 17 | - Flutter (from `Flutter`) 18 | - package_info (from `.symlinks/plugins/package_info/ios`) 19 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 20 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 21 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 22 | 23 | SPEC REPOS: 24 | trunk: 25 | - FMDB 26 | 27 | EXTERNAL SOURCES: 28 | Flutter: 29 | :path: Flutter 30 | package_info: 31 | :path: ".symlinks/plugins/package_info/ios" 32 | path_provider: 33 | :path: ".symlinks/plugins/path_provider/ios" 34 | shared_preferences: 35 | :path: ".symlinks/plugins/shared_preferences/ios" 36 | sqflite: 37 | :path: ".symlinks/plugins/sqflite/ios" 38 | 39 | SPEC CHECKSUMS: 40 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a 41 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 42 | package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 43 | path_provider: d1e9807085df1f9cc9318206cd649dc0b76be3de 44 | shared_preferences: 5033afbb22d372e15aff8ff766df9021b845f273 45 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 46 | 47 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 48 | 49 | COCOAPODS: 1.10.1 50 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/development.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/production.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/staging.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuitv/flutter_clean_architecture/d662127f5902eb1d6b95f912ebc99e11cbe561c7/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 $(DISPLAY_NAME_SUFFIX) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | flutter_clean_architecture 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/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import 'presentation/di/locator.dart'; 5 | import 'presentation/screen/app/app.dart'; 6 | 7 | void mainDelegate() async { 8 | WidgetsFlutterBinding.ensureInitialized(); 9 | 10 | SystemChrome.setPreferredOrientations([ 11 | DeviceOrientation.portraitUp, 12 | DeviceOrientation.portraitDown, 13 | ]); 14 | 15 | // SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 16 | // statusBarBrightness: Brightness.dark, 17 | // )); 18 | 19 | await setupLocator(); 20 | 21 | runApp(App()); 22 | } 23 | -------------------------------------------------------------------------------- /lib/main.dev.dart: -------------------------------------------------------------------------------- 1 | import 'package:core/core.dart'; 2 | 3 | import 'main.dart'; 4 | 5 | void main() { 6 | Core.init(Environment.DEV); 7 | mainDelegate(); 8 | } 9 | -------------------------------------------------------------------------------- /lib/main.prod.dart: -------------------------------------------------------------------------------- 1 | import 'package:core/core.dart'; 2 | 3 | import 'main.dart'; 4 | 5 | void main() { 6 | Core.init(Environment.PROD); 7 | mainDelegate(); 8 | } 9 | -------------------------------------------------------------------------------- /lib/main.staging.dart: -------------------------------------------------------------------------------- 1 | import 'package:core/core.dart'; 2 | 3 | import 'main.dart'; 4 | 5 | void main() { 6 | Core.init(Environment.STAGING); 7 | mainDelegate(); 8 | } 9 | -------------------------------------------------------------------------------- /lib/presentation/common/base_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | 3 | class BaseBloc extends BlocBase { 4 | BaseBloc() : super(false); 5 | } 6 | -------------------------------------------------------------------------------- /lib/presentation/di/locator.dart: -------------------------------------------------------------------------------- 1 | import 'package:data/data.dart'; 2 | import 'package:domain/domain.dart'; 3 | import 'package:get_it/get_it.dart'; 4 | 5 | import '../screen/app/app_bloc.dart'; 6 | import '../screen/app_bottom_bar/app_bottom_bar_bloc.dart'; 7 | import '../screen/home/home_bloc.dart'; 8 | import '../screen/home/home_setting_bloc.dart'; 9 | import '../screen/popup/floating_popup/floating_popup_bloc.dart'; 10 | import '../screen/popup/region/region_bloc.dart'; 11 | import '../screen/shopping/shopping_bloc.dart'; 12 | import '../screen/shopping/shopping_setting_bloc.dart'; 13 | 14 | final locator = GetIt.instance..allowReassignment = true; 15 | 16 | Future setupLocator() async { 17 | _registerBloc(); 18 | await Data.init(); 19 | Domain.init(); 20 | } 21 | 22 | void _registerBloc() { 23 | locator.registerFactory(() => AppBloc( 24 | apiErrorHandle: locator(), 25 | getRegions: locator(), 26 | region: locator(), 27 | )); 28 | locator.registerFactory(() => AppBottomBarBloc()); 29 | 30 | locator.registerFactory(() => HomeBloc(locator())); 31 | locator.registerFactory(() => ShoppingBloc(locator())); 32 | locator.registerFactory(() => RegionBloc( 33 | locator(), 34 | locator(), 35 | )); 36 | 37 | locator.registerFactory(() => FloatingPopupBloc(locator())); 38 | 39 | ///Home page 40 | locator.registerFactory(() => HomeSettingBloc(locator())); 41 | 42 | ///Shopping page 43 | locator.registerFactory(() => ShoppingSettingBloc(locator())); 44 | } 45 | -------------------------------------------------------------------------------- /lib/presentation/extension/screen_setting.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:domain/domain.dart'; 4 | import 'package:utils/utils.dart'; 5 | 6 | extension ScreenSettingExt on ScreenSetting { 7 | Color? get colorForHex => Utils.hexColor(this.color ?? ''); 8 | } 9 | -------------------------------------------------------------------------------- /lib/presentation/routes/app_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | import '../screen/app_bottom_bar/app_bottom_bar.dart'; 5 | import 'routes.dart'; 6 | 7 | class AppRoutes { 8 | static final navigatorKey = GlobalKey(); 9 | static final routeObserver = RouteObserver(); 10 | 11 | static Map routes = {}; 12 | 13 | static Route? onGenerateRoute(RouteSettings settings) { 14 | WidgetBuilder? builder; 15 | 16 | switch (settings.name) { 17 | case Routes.init: 18 | builder = (context) => AppBottomBarScreen(); 19 | break; 20 | 21 | default: 22 | break; 23 | } 24 | 25 | if (builder != null) { 26 | return MaterialPageRoute( 27 | settings: settings, 28 | builder: builder, 29 | ); 30 | } 31 | 32 | return null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/presentation/routes/routes.dart: -------------------------------------------------------------------------------- 1 | class Routes { 2 | static const String init = '/'; 3 | } 4 | -------------------------------------------------------------------------------- /lib/presentation/screen/app/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:core/core.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:flutter_localizations/flutter_localizations.dart'; 5 | 6 | import '../../di/locator.dart'; 7 | import '../../routes/app_routes.dart'; 8 | import '../../routes/routes.dart'; 9 | import '../../values/themes.dart'; 10 | import 'app_bloc.dart'; 11 | 12 | class App extends StatelessWidget { 13 | const App({Key? key}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return MultiBlocProvider( 18 | providers: [ 19 | BlocProvider( 20 | create: (_) => locator(), 21 | ), 22 | ], 23 | child: AppView(), 24 | ); 25 | } 26 | } 27 | 28 | class AppView extends StatefulWidget { 29 | const AppView({Key? key}) : super(key: key); 30 | 31 | @override 32 | _AppViewState createState() => _AppViewState(); 33 | } 34 | 35 | class _AppViewState extends State { 36 | @override 37 | void initState() { 38 | super.initState(); 39 | Strings.init(context); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return MaterialApp( 45 | title: 'Flutter', 46 | debugShowCheckedModeBanner: false, 47 | theme: Themes.lightTheme, 48 | locale: const Locale('vi', ''), 49 | supportedLocales: AppLocalizationsDelegate.supportedLocales, 50 | localizationsDelegates: [ 51 | AppLocalizationsDelegate.delegate, 52 | GlobalMaterialLocalizations.delegate, 53 | GlobalWidgetsLocalizations.delegate, 54 | GlobalCupertinoLocalizations.delegate, 55 | ], 56 | localeResolutionCallback: (locale, supportedLocales) { 57 | for (var supportedLocale in supportedLocales) { 58 | if (supportedLocale.languageCode == locale?.languageCode && 59 | supportedLocale.countryCode == locale?.countryCode) { 60 | return supportedLocale; 61 | } 62 | } 63 | return supportedLocales.first; 64 | }, 65 | navigatorKey: AppRoutes.navigatorKey, 66 | navigatorObservers: [ 67 | AppRoutes.routeObserver, 68 | ], 69 | initialRoute: Routes.init, 70 | onGenerateRoute: AppRoutes.onGenerateRoute, 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/presentation/screen/app/app_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:domain/domain.dart'; 3 | import 'package:equatable/equatable.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import '../popup/region/region_dialog.dart'; 7 | 8 | part 'app_state.dart'; 9 | 10 | class AppBloc extends BlocBase { 11 | final ApiErrorHandle apiErrorHandle; 12 | final GetRegions getRegions; 13 | final GetRegion region; 14 | 15 | AppBloc({ 16 | required this.apiErrorHandle, 17 | required this.getRegions, 18 | required this.region, 19 | }) : super(AppState( 20 | region: region(), 21 | )) { 22 | init(); 23 | } 24 | 25 | void init() { 26 | apiErrorHandling(); 27 | getAppRegions(); 28 | } 29 | 30 | void apiErrorHandling() { 31 | apiErrorHandle(InterceptorsWrapper( 32 | onError: (error, handle) { 33 | if (error.type == DioErrorType.response) { 34 | //Todo Logout user case 35 | } 36 | return handle.next(error); 37 | }, 38 | )); 39 | } 40 | 41 | Future getAppRegions() async { 42 | final result = await getRegions(); 43 | result.when( 44 | success: (data) { 45 | if (region() == null) { 46 | RegionDialog.show(); 47 | } 48 | }, 49 | ); 50 | } 51 | 52 | Future updateRegions(Region region) async { 53 | emit(state.copyWith(region: region)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/presentation/screen/app/app_state.dart: -------------------------------------------------------------------------------- 1 | part of '../../../presentation/screen/app/app_bloc.dart'; 2 | 3 | class AppState extends Equatable { 4 | final Region? region; 5 | 6 | const AppState({ 7 | this.region, 8 | }); 9 | 10 | @override 11 | List get props => [region]; 12 | 13 | AppState copyWith({ 14 | Region? region, 15 | }) { 16 | return AppState( 17 | region: region ?? this.region, 18 | ); 19 | } 20 | 21 | String get regionName => region?.id == Region.idSouthern ? 'Miền Nam' : 'Miền Bắc'; 22 | } 23 | -------------------------------------------------------------------------------- /lib/presentation/screen/app_bottom_bar/app_bottom_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/scheduler.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | 7 | import '../../di/locator.dart'; 8 | import '../../values/sizes.dart'; 9 | import '../home/home_screen.dart'; 10 | import '../shopping/shopping_screen.dart'; 11 | import 'app_bottom_bar_bloc.dart'; 12 | import 'widget/custom_bottom_navigation_bar.dart'; 13 | 14 | class AppBottomBarScreen extends StatelessWidget { 15 | const AppBottomBarScreen({Key? key}) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return BlocProvider( 20 | create: (_) => locator(), 21 | child: BottomBar(), 22 | ); 23 | } 24 | } 25 | 26 | class BottomBar extends StatefulWidget { 27 | const BottomBar({Key? key}) : super(key: key); 28 | 29 | @override 30 | _BottomBarState createState() => _BottomBarState(); 31 | } 32 | 33 | class _BottomBarState extends State with TickerProviderStateMixin { 34 | List pages = [ 35 | HomeScreen(), 36 | Center(child: Text('Coupon')), 37 | ShoppingScreen(), 38 | Center(child: Text('BeeFeed')), 39 | Center(child: Text('Cá nhân')), 40 | ]; 41 | 42 | late List destinationKeys; 43 | late List faders; 44 | late AnimationController hideBottomBar; 45 | 46 | @override 47 | void initState() { 48 | super.initState(); 49 | faders = List.generate( 50 | pages.length, 51 | (_) => AnimationController( 52 | vsync: this, 53 | duration: kThemeAnimationDuration, 54 | ), 55 | ); 56 | faders[context.read().state.index].value = 1.0; 57 | destinationKeys = List.generate( 58 | pages.length, 59 | (int index) => GlobalKey(), 60 | ).toList(); 61 | hideBottomBar = AnimationController( 62 | vsync: this, 63 | duration: kThemeAnimationDuration, 64 | ); 65 | SchedulerBinding.instance?.addPostFrameCallback((_) { 66 | hideBottomBar.forward(); 67 | }); 68 | } 69 | 70 | @override 71 | void didChangeDependencies() { 72 | super.didChangeDependencies(); 73 | Sizes.init( 74 | BoxConstraints( 75 | maxWidth: MediaQuery.of(context).size.width, 76 | maxHeight: MediaQuery.of(context).size.height, 77 | ), 78 | designSize: const Size(750, 1450), 79 | ); 80 | } 81 | 82 | @override 83 | void dispose() { 84 | for (AnimationController controller in faders) controller.dispose(); 85 | hideBottomBar.dispose(); 86 | super.dispose(); 87 | } 88 | 89 | @override 90 | Widget build(BuildContext context) { 91 | final currentIndex = context.watch().state; 92 | 93 | return WillPopScope( 94 | onWillPop: () async { 95 | if (currentIndex != BottomBarIndex.shopping) { 96 | context.read().emit(BottomBarIndex.shopping); 97 | return false; 98 | } 99 | return true; 100 | }, 101 | child: Scaffold( 102 | body: Stack( 103 | fit: StackFit.expand, 104 | children: List.generate(pages.length, (int index) { 105 | final Widget view = FadeTransition( 106 | opacity: faders[index].drive(CurveTween(curve: Curves.fastOutSlowIn)), 107 | child: KeyedSubtree( 108 | key: destinationKeys[index], 109 | child: pages[index], 110 | ), 111 | ); 112 | if (index == currentIndex.index) { 113 | faders[index].forward(); 114 | return view; 115 | } else { 116 | faders[index].reverse(); 117 | if (faders[index].isAnimating) { 118 | return IgnorePointer(child: view); 119 | } 120 | return Offstage(child: view); 121 | } 122 | }).toList(), 123 | ), 124 | bottomNavigationBar: CustomBottomNavigationBar(), 125 | ), 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/presentation/screen/app_bottom_bar/app_bottom_bar_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | 3 | enum BottomBarIndex { 4 | home, 5 | coupon, 6 | shopping, 7 | newFeed, 8 | user, 9 | } 10 | 11 | class AppBottomBarBloc extends BlocBase { 12 | AppBottomBarBloc() : super(BottomBarIndex.home); 13 | 14 | BottomBarIndex get currentIndex => state; 15 | 16 | void changeTab(BottomBarIndex index) { 17 | emit(index); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/presentation/screen/app_bottom_bar/widget/custom_bottom_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:core/core.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | 7 | import '../app_bottom_bar_bloc.dart'; 8 | 9 | class CustomBottomAppBar extends StatelessWidget { 10 | const CustomBottomAppBar({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | final currentIndex = context.watch().state; 15 | //final unselectedItemColor = BottomNavigationBarTheme.of(context).unselectedItemColor; 16 | 17 | return BottomAppBar( 18 | shape: CircularNotchedRectangle(), 19 | child: Container( 20 | height: 70, 21 | child: Row( 22 | mainAxisAlignment: MainAxisAlignment.spaceAround, 23 | children: [ 24 | BottomAppBarItem( 25 | onPressed: () => context.read().emit(BottomBarIndex.home), 26 | isActive: currentIndex == BottomBarIndex.home, 27 | label: Strings.tr.home, 28 | icon: Icon(CupertinoIcons.home), 29 | ), 30 | BottomAppBarItem( 31 | onPressed: () => context.read().emit(BottomBarIndex.coupon), 32 | isActive: currentIndex == BottomBarIndex.coupon, 33 | label: Strings.tr.coupon, 34 | icon: Icon(CupertinoIcons.tickets), 35 | ), 36 | BottomAppBarItem( 37 | isActive: currentIndex == BottomBarIndex.shopping, 38 | onPressed: () => null, 39 | label: Strings.tr.shopping, 40 | icon: SizedBox(), 41 | ), 42 | BottomAppBarItem( 43 | onPressed: () => context.read().emit(BottomBarIndex.newFeed), 44 | isActive: currentIndex == BottomBarIndex.newFeed, 45 | label: Strings.tr.newFeed, 46 | icon: Icon(CupertinoIcons.book), 47 | ), 48 | BottomAppBarItem( 49 | onPressed: () => context.read().emit(BottomBarIndex.user), 50 | isActive: currentIndex == BottomBarIndex.user, 51 | label: Strings.tr.user, 52 | icon: Icon(CupertinoIcons.person), 53 | ), 54 | ], 55 | ), 56 | ), 57 | ); 58 | } 59 | } 60 | 61 | class BottomAppBarItem extends StatelessWidget { 62 | static const iconSizeCenter = 45.0; 63 | 64 | const BottomAppBarItem({ 65 | Key? key, 66 | required this.isActive, 67 | required this.icon, 68 | required this.onPressed, 69 | this.label, 70 | this.iconSize = 25, 71 | this.marginBottom = 6, 72 | }) : super(key: key); 73 | 74 | final Function() onPressed; 75 | final Widget icon; 76 | final String? label; 77 | final double iconSize; 78 | 79 | final double marginBottom; 80 | final bool isActive; 81 | 82 | @override 83 | Widget build(BuildContext context) { 84 | final selectedLabelStyle = BottomNavigationBarTheme.of(context).selectedLabelStyle; 85 | final unselectedLabelStyle = BottomNavigationBarTheme.of(context).unselectedLabelStyle; 86 | 87 | return Stack( 88 | alignment: Alignment.bottomCenter, 89 | children: [ 90 | IconButton( 91 | onPressed: onPressed, 92 | padding: EdgeInsets.zero, 93 | icon: Column(children: [ 94 | Container( 95 | width: iconSize, 96 | height: iconSize, 97 | margin: EdgeInsets.only(bottom: marginBottom), 98 | child: icon, 99 | ), 100 | ]), 101 | ), 102 | if (label != null) Text(label!, style: isActive ? selectedLabelStyle : unselectedLabelStyle) 103 | ], 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/presentation/screen/app_bottom_bar/widget/custom_bottom_navigation_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:core/core.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import '../app_bottom_bar_bloc.dart'; 7 | 8 | class CustomBottomNavigationBar extends StatelessWidget { 9 | const CustomBottomNavigationBar({Key? key}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | final currentIndex = context.watch().state; 14 | final unselectedItemColor = BottomNavigationBarTheme.of(context).unselectedItemColor; 15 | 16 | return BottomNavigationBar( 17 | currentIndex: currentIndex.index, 18 | onTap: (index) { 19 | context.read().emit(BottomBarIndex.values[index]); 20 | }, 21 | items: [ 22 | CustomBottomNavigationBarItem( 23 | icon: Icon( 24 | CupertinoIcons.home, 25 | color: currentIndex != BottomBarIndex.home ? unselectedItemColor : null, 26 | ), 27 | label: Strings.tr.home, 28 | ), 29 | CustomBottomNavigationBarItem( 30 | icon: Icon( 31 | CupertinoIcons.tickets, 32 | color: currentIndex != BottomBarIndex.coupon ? unselectedItemColor : null, 33 | ), 34 | label: Strings.tr.coupon, 35 | ), 36 | CustomBottomNavigationBarItem( 37 | iconSize: CustomBottomNavigationBarItem.iconSizeCenter, 38 | marginBottom: 4, 39 | icon: Icon( 40 | CupertinoIcons.bag_fill_badge_plus, 41 | color: currentIndex != BottomBarIndex.shopping ? unselectedItemColor : null, 42 | size: 36, 43 | ), 44 | label: Strings.tr.shopping, 45 | ), 46 | CustomBottomNavigationBarItem( 47 | icon: Icon( 48 | CupertinoIcons.book, 49 | color: currentIndex != BottomBarIndex.newFeed ? unselectedItemColor : null, 50 | ), 51 | label: Strings.tr.newFeed, 52 | ), 53 | CustomBottomNavigationBarItem( 54 | icon: Icon( 55 | CupertinoIcons.person, 56 | color: currentIndex != BottomBarIndex.user ? unselectedItemColor : null, 57 | ), 58 | label: Strings.tr.user, 59 | ), 60 | ], 61 | ); 62 | } 63 | } 64 | 65 | class CustomBottomNavigationBarItem extends BottomNavigationBarItem { 66 | static const iconSize = 25.0; 67 | static const iconSizeCenter = 45.0; 68 | 69 | CustomBottomNavigationBarItem({ 70 | double iconSize = iconSize, 71 | double marginBottom = 8, 72 | required Widget icon, 73 | String? label, 74 | }) : super( 75 | label: label, 76 | icon: Container( 77 | width: iconSizeCenter, 78 | height: iconSizeCenter, 79 | alignment: Alignment.bottomCenter, 80 | child: Container( 81 | width: iconSize, 82 | height: iconSize, 83 | margin: EdgeInsets.only(bottom: marginBottom), 84 | child: icon, 85 | ), 86 | ), 87 | ); 88 | } 89 | -------------------------------------------------------------------------------- /lib/presentation/screen/home/home_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | class HomeBloc extends BlocBase { 5 | final GetHomeScreenSetting getScreenSetting; 6 | 7 | HomeBloc(this.getScreenSetting) : super(null); 8 | } 9 | -------------------------------------------------------------------------------- /lib/presentation/screen/home/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../di/locator.dart'; 5 | import '../popup/floating_popup/floating_popup.dart'; 6 | import 'home_bloc.dart'; 7 | import 'home_setting_bloc.dart'; 8 | import 'widget/home_sliver_app_bar.dart'; 9 | 10 | class HomeScreen extends StatelessWidget { 11 | const HomeScreen({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return MultiBlocProvider( 16 | providers: [ 17 | BlocProvider(create: (_) => locator()), 18 | BlocProvider(create: (_) => locator()), 19 | ], 20 | child: _HomeScreenBody(), 21 | ); 22 | } 23 | } 24 | 25 | class _HomeScreenBody extends StatelessWidget { 26 | @override 27 | Widget build(BuildContext context) { 28 | return Stack( 29 | children: [ 30 | Scaffold( 31 | backgroundColor: Colors.transparent, 32 | body: CustomScrollView( 33 | slivers: [ 34 | SliverPersistentHeader( 35 | delegate: HomeSliverAppBar(), 36 | pinned: true, 37 | ), 38 | SliverList( 39 | delegate: SliverChildListDelegate([ 40 | Container( 41 | height: 1000, 42 | color: Colors.white, 43 | ), 44 | ]), 45 | ), 46 | ], 47 | ), 48 | ), 49 | const FloatingPopup(), 50 | ], 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/presentation/screen/home/home_setting_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | class HomeSettingBloc extends BlocBase { 5 | final GetHomeScreenSetting getHomeSetting; 6 | static const initState = const ScreenSetting( 7 | text: 'Tìm kiếm sản phẩm...', 8 | color: '#949494', 9 | ); 10 | 11 | HomeSettingBloc(this.getHomeSetting) : super(initState) { 12 | init(); 13 | } 14 | 15 | Future init() async { 16 | final result = await getHomeSetting(); 17 | result.when(success: emit); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/presentation/screen/home/widget/home_bloc_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HomeBlocContainer extends StatelessWidget { 4 | final Widget? child; 5 | 6 | const HomeBlocContainer({Key? key, this.child}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Container( 11 | color: Colors.white, 12 | padding: const EdgeInsets.all(16).copyWith(right: 8), 13 | child: child, 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/presentation/screen/home/widget/home_search_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import '../../../extension/screen_setting.dart'; 6 | import '../../../widgets/search_bar.dart'; 7 | import '../home_setting_bloc.dart'; 8 | 9 | class HomeSearchBar extends StatelessWidget { 10 | const HomeSearchBar({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return BlocBuilder( 15 | buildWhen: (prev, current) => prev.text != current.text || prev.color != current.color, 16 | builder: (_, state) => SearchBarWithRegion( 17 | onPressed: () { 18 | // Todo goto search screen 19 | }, 20 | label: state.text ?? '', 21 | labelStyle: TextStyle(color: state.colorForHex), 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/presentation/screen/home/widget/home_sliver_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../../widgets/custom_sliver_app_bar_delegate.dart'; 5 | import 'home_search_bar.dart'; 6 | 7 | class HomeSliverAppBar extends CustomSliverAppBarDelegate { 8 | HomeSliverAppBar() 9 | : super( 10 | expandedHeight: 300, 11 | title: FlutterLogo(), 12 | leading: IconButton( 13 | onPressed: () => null, 14 | splashRadius: 24, 15 | icon: const Icon( 16 | CupertinoIcons.app_badge, 17 | size: 24, 18 | ), 19 | ), 20 | autoHideLeading: false, 21 | actions: [ 22 | IconButton( 23 | onPressed: () => null, 24 | splashRadius: 24, 25 | icon: Icon(CupertinoIcons.cart), 26 | ), 27 | ], 28 | searchBar: const HomeSearchBar(), 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /lib/presentation/screen/popup/floating_popup/floating_popup.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import '../../../di/locator.dart'; 6 | import '../../../values/sizes.dart'; 7 | import 'floating_popup_bloc.dart'; 8 | 9 | class FloatingPopup extends StatelessWidget { 10 | const FloatingPopup({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return BlocProvider( 15 | create: (_) => locator(), 16 | child: _FloatingPopupDrag(), 17 | ); 18 | } 19 | } 20 | 21 | class _FloatingPopupDrag extends StatefulWidget { 22 | @override 23 | _FloatingPopupDragState createState() => _FloatingPopupDragState(); 24 | } 25 | 26 | class _FloatingPopupDragState extends State<_FloatingPopupDrag> with TickerProviderStateMixin { 27 | static const double bottomPadding = 16; 28 | static const double imgSize = 100; 29 | static const double padding = 15; 30 | static const double extraSpace = imgSize / 2 + padding; 31 | 32 | static double get extraSpaceVerBottom => extraSpace + kBottomNavigationBarHeight + bottomPadding; 33 | 34 | double get extraSpaceVerTop => extraSpace + (kToolbarHeight * 2 + MediaQuery.of(context).padding.top); 35 | 36 | Offset position = Offset( 37 | Sizes().screenWidth - extraSpace, 38 | Sizes().screenHeight - extraSpaceVerBottom - bottomPadding, 39 | ); 40 | late Animation _animation; 41 | late AnimationController _animationController; 42 | 43 | void onPressed(Popup? popup) { 44 | if (popup?.deepLink == null) return; 45 | // todo navigate page 46 | } 47 | 48 | @override 49 | void initState() { 50 | super.initState(); 51 | context.read().init(); 52 | } 53 | 54 | @override 55 | void dispose() { 56 | _animationController.dispose(); 57 | super.dispose(); 58 | } 59 | 60 | @override 61 | Widget build(BuildContext context) { 62 | final popup = context.watch().state; 63 | if (popup == null) return SizedBox(); 64 | return Positioned( 65 | top: position.dy - extraSpace, 66 | left: position.dx - extraSpace, 67 | child: GestureDetector( 68 | onPanUpdate: _onPanUpdate, 69 | onPanEnd: _onPanEnd, 70 | onTap: () => onPressed(popup), 71 | child: Stack(alignment: Alignment.topRight, children: [ 72 | Padding( 73 | padding: const EdgeInsets.all(padding), 74 | child: Container( 75 | width: imgSize, 76 | height: imgSize, 77 | child: FlutterLogo(), 78 | ), 79 | ), 80 | IconButton( 81 | icon: Icon( 82 | Icons.cancel, 83 | color: const Color(0xFFA4A4A4), 84 | ), 85 | padding: EdgeInsets.zero, 86 | onPressed: () => context.read().emit(null), 87 | ), 88 | ]), 89 | ), 90 | ); 91 | } 92 | 93 | void _animateToEdge({double? from, double? to}) { 94 | _animationController = AnimationController( 95 | vsync: this, 96 | duration: Duration(milliseconds: Sizes().screenWidth ~/ 0.6), 97 | ); 98 | _animation = Tween( 99 | begin: from, 100 | end: to, 101 | ).chain(CurveTween(curve: Curves.easeOutBack)).animate(_animationController) 102 | ..addListener(() { 103 | setState(() { 104 | position = Offset(_animation.value, position.dy); 105 | }); 106 | }); 107 | _animationController.forward(); 108 | } 109 | 110 | void _moveToLeftEdge() { 111 | _animateToEdge( 112 | from: position.dx, 113 | to: extraSpace, 114 | ); 115 | } 116 | 117 | void _moveToRightEdge() { 118 | _animateToEdge( 119 | from: position.dx, 120 | to: Sizes().screenWidth - extraSpace, 121 | ); 122 | } 123 | 124 | void _onPanUpdate(DragUpdateDetails details) { 125 | setState(() { 126 | position = details.globalPosition; 127 | if (position.dx + extraSpace > Sizes().screenWidth) 128 | position = Offset(Sizes().screenWidth - extraSpace, position.dy); 129 | if (position.dx - extraSpace < 0) position = Offset(extraSpace, position.dy); 130 | if (position.dy + extraSpaceVerBottom > Sizes().screenHeight) 131 | position = Offset(position.dx, Sizes().screenHeight - extraSpaceVerBottom); 132 | if (position.dy - extraSpaceVerTop < 0) position = Offset(position.dx, extraSpaceVerTop); 133 | }); 134 | } 135 | 136 | void _onPanEnd(DragEndDetails details) { 137 | double spaceLeft = position.dx; 138 | double spaceRight = Sizes().screenWidth - spaceLeft; 139 | if (spaceLeft < spaceRight) 140 | _moveToLeftEdge(); 141 | else 142 | _moveToRightEdge(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/presentation/screen/popup/floating_popup/floating_popup_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | class FloatingPopupBloc extends BlocBase { 5 | FloatingPopupBloc(this.getFloatPopup) : super(null); 6 | 7 | final GetFloatPopup getFloatPopup; 8 | 9 | void init() async { 10 | final result = await getFloatPopup(); 11 | result.when(success: (popup) { 12 | if (popup.status == 'active') emit(popup); 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/presentation/screen/popup/region/region_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | class RegionBloc extends BlocBase { 5 | RegionBloc( 6 | this._getRegionsLocal, 7 | this._updateRegion, 8 | ) : super(false); 9 | 10 | final GetRegionsLocal _getRegionsLocal; 11 | final UpdateRegion _updateRegion; 12 | 13 | List get getRegions => _getRegionsLocal(); 14 | 15 | Future updateRegion(Region region) async { 16 | //Todo xử lý status loading 17 | final result = await _updateRegion(region); 18 | return result; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/presentation/screen/popup/region/region_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:core/core.dart'; 2 | import 'package:domain/domain.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/widgets.dart'; 6 | import 'package:flutter_bloc/flutter_bloc.dart'; 7 | 8 | import '../../../di/locator.dart'; 9 | import '../../../routes/app_routes.dart'; 10 | import '../../app/app_bloc.dart'; 11 | import 'region_bloc.dart'; 12 | 13 | class RegionDialog extends StatelessWidget { 14 | const RegionDialog({Key? key}) : super(key: key); 15 | 16 | static Future show({ 17 | BuildContext? context, 18 | bool barrierDismissible = false, 19 | }) async { 20 | final appContext = AppRoutes.navigatorKey.currentState?.overlay?.context; 21 | if ((context ?? appContext) == null) throw 'Context call null'; 22 | final result = await showDialog( 23 | context: (context ?? appContext)!, 24 | barrierDismissible: barrierDismissible, 25 | builder: (_) => BlocProvider( 26 | create: (_) => locator(), 27 | child: const RegionDialog(), 28 | ), 29 | ); 30 | return result; 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return WillPopScope( 36 | onWillPop: () => Future.value(false), 37 | child: SimpleDialog( 38 | titlePadding: EdgeInsets.zero, 39 | contentPadding: const EdgeInsets.all(15), 40 | title: const RegionTitle(), 41 | children: [ 42 | Container( 43 | color: Colors.white, 44 | child: BlocBuilder( 45 | builder: (context, state) { 46 | return Row(children: [ 47 | Expanded( 48 | child: RegionButton( 49 | region: context.read().getRegions.first, 50 | ), 51 | ), 52 | const SizedBox(width: 15), 53 | Expanded( 54 | child: RegionButton( 55 | region: context.read().getRegions.last, 56 | ), 57 | ), 58 | ]); 59 | }, 60 | ), 61 | ), 62 | ], 63 | ), 64 | ); 65 | } 66 | } 67 | 68 | class RegionTitle extends StatelessWidget { 69 | const RegionTitle({Key? key}) : super(key: key); 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | return Container( 74 | padding: const EdgeInsets.all(15), 75 | decoration: BoxDecoration( 76 | color: Theme.of(context).primaryColor, 77 | borderRadius: BorderRadius.vertical( 78 | top: Radius.circular(10), 79 | )), 80 | child: Row( 81 | children: [ 82 | Icon( 83 | CupertinoIcons.location_solid, 84 | size: 18, 85 | color: Colors.white, 86 | ), 87 | const SizedBox(width: 10), 88 | Expanded( 89 | child: Text( 90 | Strings.tr.titleLocation, 91 | style: TextStyle(color: Colors.white), 92 | )), 93 | ], 94 | ), 95 | ); 96 | } 97 | } 98 | 99 | class RegionButton extends StatelessWidget { 100 | final Region region; 101 | 102 | const RegionButton({ 103 | Key? key, 104 | required this.region, 105 | }) : super(key: key); 106 | 107 | @override 108 | Widget build(BuildContext context) { 109 | return ElevatedButton( 110 | style: ElevatedButton.styleFrom( 111 | padding: EdgeInsets.symmetric(horizontal: 5, vertical: 15), 112 | shape: RoundedRectangleBorder( 113 | borderRadius: BorderRadius.all(Radius.circular(10)), 114 | side: BorderSide(color: Theme.of(context).primaryColor), 115 | ), 116 | primary: Colors.white, 117 | onPrimary: Theme.of(context).primaryColor, 118 | //textStyle: TextStyle(color: Theme.of(context).primaryColor), 119 | ), 120 | onPressed: () async { 121 | final result = await context.read().updateRegion(region); 122 | if (result) { 123 | Navigator.of(context).pop(); 124 | context.read().updateRegions(region); 125 | } 126 | }, 127 | child: FittedBox( 128 | fit: BoxFit.scaleDown, 129 | child: Text( 130 | region.name, 131 | textAlign: TextAlign.center, 132 | ), 133 | ), 134 | ); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /lib/presentation/screen/shopping/shopping_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | class ShoppingBloc extends BlocBase { 5 | final GetShoppingScreenSetting getScreenSetting; 6 | 7 | ShoppingBloc(this.getScreenSetting) : super(null); 8 | 9 | Future getPageSetting() async { 10 | final result = await getScreenSetting(); 11 | print('isSuccessful: ${result.isSuccessful}'); 12 | result.when( 13 | success: (data) => print('data: ${data.text}'), 14 | error: (error) => print(error.message), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/presentation/screen/shopping/shopping_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../../di/locator.dart'; 5 | import 'shopping_bloc.dart'; 6 | import 'shopping_setting_bloc.dart'; 7 | import 'widget/shopping_sliver_app_bar.dart'; 8 | 9 | class ShoppingScreen extends StatelessWidget { 10 | const ShoppingScreen({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return MultiBlocProvider( 15 | providers: [ 16 | BlocProvider(create: (_) => locator()), 17 | BlocProvider(create: (_) => locator()), 18 | ], 19 | child: _ShoppingScreenBody(), 20 | ); 21 | } 22 | } 23 | 24 | class _ShoppingScreenBody extends StatelessWidget { 25 | @override 26 | Widget build(BuildContext context) { 27 | return Stack(children: [ 28 | Scaffold( 29 | backgroundColor: Colors.transparent, 30 | body: CustomScrollView( 31 | slivers: [ 32 | SliverPersistentHeader( 33 | delegate: ShoppingSliverAppBar(), 34 | pinned: true, 35 | ), 36 | SliverList( 37 | delegate: SliverChildListDelegate([ 38 | Container( 39 | height: 10000, 40 | color: Colors.white, 41 | ), 42 | ]), 43 | ), 44 | ], 45 | ), 46 | ), 47 | ]); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/presentation/screen/shopping/shopping_setting_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | class ShoppingSettingBloc extends BlocBase { 5 | final GetShoppingScreenSetting _getShoppingScreenSetting; 6 | static const initState = const ScreenSetting( 7 | text: 'Tìm kiếm sản phẩm...', 8 | color: '#949494', 9 | ); 10 | 11 | ShoppingSettingBloc(this._getShoppingScreenSetting) : super(initState) { 12 | init(); 13 | } 14 | 15 | Future init() async { 16 | final result = await _getShoppingScreenSetting(); 17 | result.when(success: emit); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/presentation/screen/shopping/widget/shopping_search_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:domain/domain.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | 5 | import '../../../extension/screen_setting.dart'; 6 | import '../../../widgets/search_bar.dart'; 7 | import '../shopping_setting_bloc.dart'; 8 | 9 | class ShoppingSearchBar extends StatelessWidget { 10 | const ShoppingSearchBar({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return BlocBuilder( 15 | buildWhen: (prev, current) => prev.text != current.text || prev.color != current.color, 16 | builder: (_, state) => SearchBarWithRegion( 17 | onPressed: () { 18 | // Todo goto search screen 19 | }, 20 | label: state.text ?? '', 21 | labelStyle: TextStyle(color: state.colorForHex), 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/presentation/screen/shopping/widget/shopping_sliver_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../../../widgets/custom_sliver_app_bar_delegate.dart'; 5 | import 'shopping_search_bar.dart'; 6 | 7 | class ShoppingSliverAppBar extends CustomSliverAppBarDelegate { 8 | ShoppingSliverAppBar() 9 | : super( 10 | expandedHeight: 300, 11 | title: const FlutterLogo(size: 24), 12 | leading: IconButton( 13 | onPressed: () { 14 | /// todo open category 15 | }, 16 | splashRadius: 24, 17 | icon: const Icon(Icons.menu_rounded, size: 32), 18 | ), 19 | autoHideLeading: false, 20 | actions: [ 21 | IconButton( 22 | onPressed: () { 23 | ///todo open cart 24 | }, 25 | splashRadius: 24, 26 | icon: Icon(CupertinoIcons.cart_fill), 27 | ), 28 | ], 29 | searchBar: const ShoppingSearchBar(), 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /lib/presentation/values/assets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Assets { 4 | static const appIcon = AssetImage('assets/app_icon.png'); 5 | } 6 | -------------------------------------------------------------------------------- /lib/presentation/values/constant.dart: -------------------------------------------------------------------------------- 1 | class Constant {} 2 | -------------------------------------------------------------------------------- /lib/presentation/values/sizes.dart: -------------------------------------------------------------------------------- 1 | library flutter_size; 2 | 3 | import 'dart:math'; 4 | import 'dart:ui' as ui; 5 | 6 | import 'package:flutter/material.dart'; 7 | 8 | class Sizes { 9 | static const Size defaultSize = Size(750, 1500); 10 | static late Sizes _instance; 11 | 12 | /// Size of the phone in UI Design , dp 13 | late Size uiSize; 14 | 15 | late Orientation _orientation; 16 | 17 | late double _pixelRatio; 18 | late double _textScaleFactor; 19 | late double _screenWidth; 20 | late double _screenHeight; 21 | late double _statusBarHeight; 22 | late double _bottomBarHeight; 23 | 24 | Sizes._(); 25 | 26 | factory Sizes() => _instance; 27 | 28 | static void init( 29 | BoxConstraints constraints, { 30 | Orientation orientation = Orientation.portrait, 31 | Size designSize = defaultSize, 32 | }) { 33 | _instance = Sizes._() 34 | ..uiSize = designSize 35 | .._orientation = orientation 36 | .._screenWidth = constraints.maxWidth 37 | .._screenHeight = constraints.maxHeight; 38 | 39 | final window = WidgetsBinding.instance?.window ?? ui.window; 40 | _instance._pixelRatio = window.devicePixelRatio; 41 | _instance._statusBarHeight = window.padding.top; 42 | _instance._bottomBarHeight = window.padding.bottom; 43 | _instance._textScaleFactor = window.textScaleFactor; 44 | } 45 | 46 | ///Get screen orientation 47 | Orientation get orientation => _orientation; 48 | 49 | /// The number of font pixels for each logical pixel. 50 | double get textScaleFactor => _textScaleFactor; 51 | 52 | /// The size of the media in logical pixels (e.g, the size of the screen). 53 | double get pixelRatio => _pixelRatio; 54 | 55 | /// The horizontal extent of this size. 56 | double get screenWidth => _screenWidth; 57 | 58 | ///The vertical extent of this size. dp 59 | double get screenHeight => _screenHeight; 60 | 61 | /// The offset from the top, in dp 62 | double get statusBarHeight => _statusBarHeight / _pixelRatio; 63 | 64 | /// 底部安全区距离 dp 65 | /// The offset from the bottom, in dp 66 | double get bottomBarHeight => _bottomBarHeight / _pixelRatio; 67 | 68 | /// The ratio of actual width to UI design 69 | double get scaleWidth => _screenWidth / uiSize.width; 70 | 71 | /// /// The ratio of actual height to UI design 72 | double get scaleHeight => _screenHeight / uiSize.height; 73 | 74 | double get scaleText => min(scaleWidth, scaleHeight); 75 | 76 | /// Adapted to the device width of the UI Design. 77 | /// Height can also be adapted according to this to ensure no deformation , 78 | /// if you want a square 79 | double setWidth(num width) => width * scaleWidth; 80 | 81 | /// Highly adaptable to the device according to UI Design 82 | /// It is recommended to use this method to achieve a high degree of adaptation 83 | /// when it is found that one screen in the UI design 84 | /// does not match the current style effect, or if there is a difference in shape. 85 | double setHeight(num height) => height * scaleHeight; 86 | 87 | ///Adapt according to the smaller of width or height 88 | double radius(num r) => r * scaleText; 89 | 90 | ///Font size adaptation method 91 | ///- [fontSize] The size of the font on the UI design, in dp. 92 | double setSp(num fontSize) => fontSize * scaleText; 93 | } 94 | 95 | extension SizeExtension on num { 96 | ///[Sizes.setWidth] 97 | double get w => Sizes().setWidth(this); 98 | 99 | ///[Sizes.setHeight] 100 | double get h => Sizes().setHeight(this); 101 | 102 | ///[Sizes.radius] 103 | double get r => Sizes().radius(this); 104 | 105 | ///[Sizes.setSp] 106 | double get sp => Sizes().setSp(this); 107 | 108 | ///Multiple of screen width 109 | double get sw => Sizes().screenWidth * this; 110 | 111 | ///Multiple of screen height 112 | double get sh => Sizes().screenHeight * this; 113 | } 114 | -------------------------------------------------------------------------------- /lib/presentation/values/themes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class Themes { 5 | Themes._(); 6 | 7 | static const colorScheme = const ColorScheme( 8 | primary: const Color(0xFFfe832a), 9 | primaryVariant: const Color(0xFFfe832a), 10 | secondary: const Color(0xFFFDB434), 11 | secondaryVariant: const Color(0xFFFDB434), 12 | surface: Colors.white, 13 | background: Colors.white, 14 | error: const Color(0xffe44034), 15 | onPrimary: Colors.white, 16 | onSecondary: Colors.black, 17 | onSurface: Colors.black, 18 | onBackground: Colors.black, 19 | onError: Colors.white, 20 | brightness: Brightness.light, 21 | ); 22 | 23 | static final ThemeData lightTheme = ThemeData( 24 | primaryColor: colorScheme.primary, 25 | colorScheme: colorScheme, 26 | backgroundColor: Colors.white, 27 | scaffoldBackgroundColor: const Color(0xFFF3F3F3), 28 | bottomNavigationBarTheme: BottomNavigationBarThemeData( 29 | backgroundColor: Colors.white, 30 | showSelectedLabels: true, 31 | showUnselectedLabels: true, 32 | selectedItemColor: colorScheme.primary, 33 | selectedLabelStyle: TextStyle( 34 | color: colorScheme.primary, 35 | fontSize: 14, 36 | fontWeight: FontWeight.bold, 37 | ), 38 | unselectedItemColor: const Color(0xFFA4A4A4), 39 | unselectedLabelStyle: TextStyle( 40 | color: const Color(0xFFA4A4A4), 41 | fontSize: 14, 42 | ), 43 | type: BottomNavigationBarType.fixed, 44 | ), 45 | splashColor: colorScheme.primary.withOpacity(0.26), 46 | disabledColor: const Color(0xFFA4A4A4), 47 | dialogTheme: DialogTheme( 48 | backgroundColor: Colors.white, 49 | shape: RoundedRectangleBorder( 50 | borderRadius: const BorderRadius.all(Radius.circular(10)), 51 | ), 52 | ), 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /lib/presentation/widgets/choose_region.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import '../screen/app/app_bloc.dart'; 5 | import '../screen/popup/region/region_dialog.dart'; 6 | 7 | class ChooseRegion extends StatelessWidget { 8 | const ChooseRegion({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return TextButton( 13 | onPressed: RegionDialog.show, 14 | child: Row(children: [ 15 | BlocBuilder( 16 | buildWhen: (prev, current) => prev.region != current.region, 17 | builder: (_, state) => Text( 18 | state.regionName, 19 | style: TextStyle( 20 | fontWeight: FontWeight.w500, 21 | color: Theme.of(context).disabledColor, 22 | ), 23 | )), 24 | const SizedBox(width: 2), 25 | Icon( 26 | Icons.keyboard_arrow_down, 27 | color: Theme.of(context).disabledColor, 28 | size: 16, 29 | ), 30 | ]), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/presentation/widgets/custom_sliver_app_bar_delegate.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import '../values/sizes.dart'; 6 | 7 | class CustomSliverAppBarDelegate extends SliverPersistentHeaderDelegate { 8 | static double defaultExpandedHeight = kToolbarHeight * 2 + Sizes().statusBarHeight - 10; 9 | 10 | CustomSliverAppBarDelegate({ 11 | double? expandedHeight, 12 | required this.searchBar, 13 | this.background, 14 | this.title, 15 | List? actions, 16 | this.leading, 17 | this.backgroundColor, 18 | this.autoHideLeading, 19 | }) : this.expandedHeight = expandedHeight ?? defaultExpandedHeight, 20 | this.actions = actions?..add(SizedBox(width: 4)); 21 | 22 | final Widget? title; 23 | final Widget? background; 24 | final Widget searchBar; 25 | final List? actions; 26 | final Widget? leading; 27 | final double expandedHeight; 28 | final Color? backgroundColor; 29 | final bool? autoHideLeading; 30 | 31 | @override 32 | Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { 33 | final shrinkOffsetPosition = min(40, shrinkOffset / (maxExtent - minExtent) * 40).toDouble(); 34 | final double show = shrinkOffset / maxExtent; 35 | final double hide = 1 - shrinkOffset / maxExtent; 36 | return Stack(children: [ 37 | if (background != null) background!, 38 | AppBar( 39 | elevation: 0, 40 | backgroundColor: Theme.of(context).primaryColor, 41 | centerTitle: true, 42 | leading: autoHideLeading != null 43 | ? IgnorePointer( 44 | ignoring: (autoHideLeading! ? hide : show) < 0.9, 45 | child: Opacity( 46 | opacity: autoHideLeading! ? hide : show, 47 | child: leading, 48 | ), 49 | ) 50 | : leading, 51 | title: title != null 52 | ? Opacity( 53 | opacity: hide, 54 | child: title, 55 | ) 56 | : null, 57 | actions: actions, 58 | ), 59 | Positioned( 60 | left: 8 + shrinkOffsetPosition, 61 | right: 8 + shrinkOffsetPosition, 62 | bottom: 0, 63 | child: Padding( 64 | padding: const EdgeInsets.all(8.0), 65 | child: searchBar, 66 | ), 67 | ), 68 | ]); 69 | } 70 | 71 | @override 72 | double get maxExtent => defaultExpandedHeight; 73 | 74 | @override 75 | double get minExtent => kToolbarHeight + Sizes().statusBarHeight; 76 | 77 | @override 78 | bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) => true; 79 | } 80 | -------------------------------------------------------------------------------- /lib/presentation/widgets/search_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'choose_region.dart'; 5 | 6 | class SearchBarWithRegion extends SearchBar { 7 | const SearchBarWithRegion({ 8 | required Function() onPressed, 9 | required String label, 10 | TextStyle? labelStyle, 11 | BorderRadiusGeometry? borderRadius, 12 | Widget? prefixIcon, 13 | Widget suffixIcon = const ChooseRegion(), 14 | }) : super( 15 | onPressed: onPressed, 16 | label: label, 17 | labelStyle: labelStyle, 18 | borderRadius: borderRadius, 19 | suffixIcon: suffixIcon, 20 | ); 21 | } 22 | 23 | class SearchBar extends StatelessWidget { 24 | static const double height = 40; 25 | 26 | final Function() onPressed; 27 | final String label; 28 | final TextStyle? labelStyle; 29 | final BorderRadiusGeometry? borderRadius; 30 | final Widget prefixIcon; 31 | final Widget? suffixIcon; 32 | 33 | const SearchBar({ 34 | Key? key, 35 | required this.onPressed, 36 | required this.label, 37 | this.labelStyle, 38 | this.borderRadius, 39 | this.prefixIcon = const Icon( 40 | CupertinoIcons.search, 41 | size: 22, 42 | color: Color(0xffBCBCBC), 43 | ), 44 | this.suffixIcon, 45 | }) : super(key: key); 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | return Container( 50 | height: height, 51 | child: ElevatedButton( 52 | onPressed: onPressed, 53 | style: ElevatedButton.styleFrom( 54 | primary: Colors.white, 55 | onPrimary: Theme.of(context).primaryColor, 56 | padding: const EdgeInsets.symmetric(horizontal: 5), 57 | shape: RoundedRectangleBorder( 58 | borderRadius: borderRadius ?? BorderRadius.circular(18), 59 | ), 60 | ), 61 | child: Row(children: [ 62 | Padding( 63 | padding: const EdgeInsets.symmetric(horizontal: 6), 64 | child: prefixIcon, 65 | ), 66 | Expanded( 67 | child: Text( 68 | label, 69 | maxLines: 1, 70 | textAlign: TextAlign.start, 71 | overflow: TextOverflow.ellipsis, 72 | style: labelStyle, 73 | )), 74 | Padding( 75 | padding: const EdgeInsets.only(left: 6), 76 | child: suffixIcon, 77 | ), 78 | ]), 79 | ), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /plugin/utils/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/ephemeral 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | 71 | # Exceptions to above rules. 72 | !**/ios/**/default.mode1v3 73 | !**/ios/**/default.mode2v3 74 | !**/ios/**/default.pbxuser 75 | !**/ios/**/default.perspectivev3 76 | -------------------------------------------------------------------------------- /plugin/utils/.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: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /plugin/utils/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /plugin/utils/README.md: -------------------------------------------------------------------------------- 1 | # utils 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Dart 8 | [package](https://flutter.dev/developing-packages/), 9 | a library module containing code that can be shared easily across 10 | multiple Flutter or Dart projects. 11 | 12 | For help getting started with Flutter, view our 13 | [online documentation](https://flutter.dev/docs), which offers tutorials, 14 | samples, guidance on mobile development, and a full API reference. 15 | -------------------------------------------------------------------------------- /plugin/utils/lib/utils.dart: -------------------------------------------------------------------------------- 1 | library utils; 2 | 3 | import 'dart:ui'; 4 | 5 | import 'package:intl/intl.dart'; 6 | 7 | class Utils { 8 | static final _formatter = NumberFormat.currency( 9 | symbol: "₫", 10 | decimalDigits: 0, 11 | customPattern: "##,###,###¤", 12 | ); 13 | 14 | static String formatMoney(num value) { 15 | return _formatter.format(value); 16 | } 17 | 18 | static int daysBetween(DateTime from, DateTime to) { 19 | try { 20 | final dFrom = DateTime(from.year, from.month, from.day); 21 | final dTo = DateTime(to.year, to.month, to.day); 22 | return (dTo.difference(dFrom).inHours / 24).round(); 23 | } catch (e) { 24 | return 0; 25 | } 26 | } 27 | 28 | static Color? hexColor(String colorCode) { 29 | var color = colorCode.toUpperCase().replaceAll("#", ""); 30 | if (color.length == 6) { 31 | color = "FF$color"; 32 | } 33 | if (color.length == 8) { 34 | try { 35 | return Color(int.parse("0x$color")); 36 | } catch (_) {} 37 | } 38 | return null; 39 | } 40 | 41 | static T? parseJson(String key, Map? json, {T? defaultValue}) { 42 | if (json == null) return defaultValue; 43 | try { 44 | return json.containsKey(key) && json[key] is T ? json[key] : defaultValue; 45 | } catch (e) { 46 | return defaultValue; 47 | } 48 | } 49 | 50 | static int? parseJsonToInt(String key, Map? json) { 51 | if (json != null) { 52 | final value = parseJson(key, json); 53 | if (value is num) return value.toInt(); 54 | if (value is String) return int.tryParse(value); 55 | } 56 | } 57 | 58 | static double? parseJsonToDouble(String key, Map? json) { 59 | if (json != null) { 60 | final value = parseJson(key, json); 61 | if (value is num) return value.toDouble(); 62 | if (value is String) return double.tryParse(value); 63 | } 64 | } 65 | 66 | static List parseJsonToList( 67 | String key, 68 | Map? json, 69 | T Function(Map) factoryFunction, 70 | ) { 71 | if (json == null) return []; 72 | final list = parseJson(key, json) ?? []; 73 | return listFromJson(list, factoryFunction); 74 | } 75 | 76 | static List listFromJson(List json, T Function(Map) factoryFunction) { 77 | final list = []; 78 | json.forEach((element) { 79 | if (element is Map) { 80 | final value = factoryFunction(element); 81 | list.add(value); 82 | } 83 | }); 84 | return list; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /plugin/utils/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.2.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | intl: 64 | dependency: "direct main" 65 | description: 66 | name: intl 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "0.17.0" 70 | matcher: 71 | dependency: transitive 72 | description: 73 | name: matcher 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "0.12.10" 77 | meta: 78 | dependency: transitive 79 | description: 80 | name: meta 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.7.0" 84 | path: 85 | dependency: transitive 86 | description: 87 | name: path 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "1.8.0" 91 | sky_engine: 92 | dependency: transitive 93 | description: flutter 94 | source: sdk 95 | version: "0.0.99" 96 | source_span: 97 | dependency: transitive 98 | description: 99 | name: source_span 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.8.1" 103 | stack_trace: 104 | dependency: transitive 105 | description: 106 | name: stack_trace 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "1.10.0" 110 | stream_channel: 111 | dependency: transitive 112 | description: 113 | name: stream_channel 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "2.1.0" 117 | string_scanner: 118 | dependency: transitive 119 | description: 120 | name: string_scanner 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.1.0" 124 | term_glyph: 125 | dependency: transitive 126 | description: 127 | name: term_glyph 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.2.0" 131 | test_api: 132 | dependency: transitive 133 | description: 134 | name: test_api 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "0.4.2" 138 | typed_data: 139 | dependency: transitive 140 | description: 141 | name: typed_data 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "1.3.0" 145 | vector_math: 146 | dependency: transitive 147 | description: 148 | name: vector_math 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "2.1.0" 152 | sdks: 153 | dart: ">=2.12.0 <3.0.0" 154 | flutter: ">=1.17.0" 155 | -------------------------------------------------------------------------------- /plugin/utils/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: utils 2 | description: A new Flutter project. 3 | version: 0.0.1 4 | publish_to: none 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | intl: ^0.17.0 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | flutter: 21 | -------------------------------------------------------------------------------- /plugin/utils/test/utils_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:utils/utils.dart'; 5 | 6 | void main() { 7 | test('formatMoney', () { 8 | expect(Utils.formatMoney(100), '100₫'); 9 | expect(Utils.formatMoney(1000), '1,000₫'); 10 | expect(Utils.formatMoney(10000), '10,000₫'); 11 | expect(Utils.formatMoney(100000), '100,000₫'); 12 | expect(Utils.formatMoney(1000000), '1,000,000₫'); 13 | 14 | expect(Utils.formatMoney(100.0), '100₫'); 15 | expect(Utils.formatMoney(1000.1), '1,000₫'); 16 | expect(Utils.formatMoney(10000.4), '10,000₫'); 17 | expect(Utils.formatMoney(10000.55), '10,001₫'); 18 | expect(Utils.formatMoney(100000.999), '100,001₫'); 19 | expect(Utils.formatMoney(1000000.9999), '1,000,001₫'); 20 | }); 21 | 22 | test('daysBetween', () { 23 | expect(Utils.daysBetween(DateTime(2021), DateTime(2021, 1, 2)), 1); 24 | expect(Utils.daysBetween(DateTime(2021), DateTime(2021, 1, 3)), 2); 25 | expect(Utils.daysBetween(DateTime(2021, 1, 0), DateTime(2021)), 1); 26 | expect(Utils.daysBetween(DateTime(2021), DateTime(2021, 2)), 31); 27 | expect(Utils.daysBetween(DateTime(2021), DateTime(2021, 1, 1, 12)), 0); 28 | expect(Utils.daysBetween(DateTime(2021), DateTime(2021, 1, 1, 48)), 2); 29 | }); 30 | 31 | test('hexColor', () { 32 | expect(Utils.hexColor('adfggn'), null); 33 | expect(Utils.hexColor('5ADFCBD'), null); 34 | expect(Utils.hexColor('ADFCBD'), Color(0xffADFCBD)); 35 | expect(Utils.hexColor('ffadfcbd'), Color(0xffADFCBD)); 36 | }); 37 | 38 | test('parseJson', () { 39 | dynamic jsonNull = null; 40 | final json = { 41 | 'aaa': 123, 42 | }; 43 | expect(Utils.parseJson('', null), null); 44 | expect(Utils.parseJson('', jsonNull), null); 45 | expect(Utils.parseJson('', json), null); 46 | expect(Utils.parseJson('aaa', jsonNull), null); 47 | expect(Utils.parseJson('aaa', json), 123); 48 | expect(Utils.parseJson('bbb', json), null); 49 | expect(Utils.parseJson('', json, defaultValue: 0), 0); 50 | expect(Utils.parseJson('aaa', json, defaultValue: 0), 123); 51 | expect(Utils.parseJson('bbb', json, defaultValue: 0), 0); 52 | }); 53 | 54 | test('parseJsonToInt', () { 55 | dynamic jsonNull = null; 56 | final json = { 57 | 'aaa': 123, 58 | 'bbb': 123.0, 59 | 'ccc': '123', 60 | 'ddd': '12a', 61 | }; 62 | expect(Utils.parseJsonToInt('', null), null); 63 | expect(Utils.parseJsonToInt('', jsonNull), null); 64 | expect(Utils.parseJsonToInt('', json), null); 65 | expect(Utils.parseJsonToInt('a', json), null); 66 | expect(Utils.parseJsonToInt('aaa', jsonNull), null); 67 | expect(Utils.parseJsonToInt('aaa', json), 123); 68 | expect(Utils.parseJsonToInt('bbb', json), 123); 69 | expect(Utils.parseJsonToInt('ccc', json), 123); 70 | expect(Utils.parseJsonToInt('ddd', json), null); 71 | }); 72 | 73 | test('parseJsonToDouble', () { 74 | dynamic jsonNull = null; 75 | final json = { 76 | 'aaa': 123, 77 | 'bbb': 123.0, 78 | 'ccc': '123', 79 | 'ddd': '12a', 80 | }; 81 | expect(Utils.parseJsonToDouble('', null), null); 82 | expect(Utils.parseJsonToDouble('', jsonNull), null); 83 | expect(Utils.parseJsonToDouble('', json), null); 84 | expect(Utils.parseJsonToDouble('a', json), null); 85 | expect(Utils.parseJsonToDouble('aaa', jsonNull), null); 86 | expect(Utils.parseJsonToDouble('aaa', json), 123.0); 87 | expect(Utils.parseJsonToDouble('bbb', json), 123.0); 88 | expect(Utils.parseJsonToDouble('ccc', json), 123.0); 89 | expect(Utils.parseJsonToDouble('ddd', json), null); 90 | }); 91 | } 92 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.1" 11 | bloc: 12 | dependency: transitive 13 | description: 14 | name: bloc 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "7.2.0" 18 | boolean_selector: 19 | dependency: transitive 20 | description: 21 | name: boolean_selector 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.1.0" 25 | cached_network_image: 26 | dependency: "direct main" 27 | description: 28 | name: cached_network_image 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "3.1.0" 32 | cached_network_image_platform_interface: 33 | dependency: transitive 34 | description: 35 | name: cached_network_image_platform_interface 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.0.0" 39 | cached_network_image_web: 40 | dependency: transitive 41 | description: 42 | name: cached_network_image_web 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.0.1" 46 | characters: 47 | dependency: transitive 48 | description: 49 | name: characters 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.1.0" 53 | charcode: 54 | dependency: transitive 55 | description: 56 | name: charcode 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.3.1" 60 | clock: 61 | dependency: transitive 62 | description: 63 | name: clock 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.1.0" 67 | collection: 68 | dependency: transitive 69 | description: 70 | name: collection 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.15.0" 74 | core: 75 | dependency: "direct main" 76 | description: 77 | path: core 78 | relative: true 79 | source: path 80 | version: "0.0.1" 81 | crypto: 82 | dependency: transitive 83 | description: 84 | name: crypto 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "3.0.1" 88 | cupertino_icons: 89 | dependency: "direct main" 90 | description: 91 | name: cupertino_icons 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.0.3" 95 | data: 96 | dependency: "direct main" 97 | description: 98 | path: data 99 | relative: true 100 | source: path 101 | version: "0.0.1" 102 | dio: 103 | dependency: "direct main" 104 | description: 105 | name: dio 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "4.0.0" 109 | domain: 110 | dependency: "direct main" 111 | description: 112 | path: domain 113 | relative: true 114 | source: path 115 | version: "0.0.1" 116 | equatable: 117 | dependency: "direct main" 118 | description: 119 | name: equatable 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "2.0.3" 123 | fake_async: 124 | dependency: transitive 125 | description: 126 | name: fake_async 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.2.0" 130 | ffi: 131 | dependency: transitive 132 | description: 133 | name: ffi 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "1.1.2" 137 | file: 138 | dependency: transitive 139 | description: 140 | name: file 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "6.1.2" 144 | flutter: 145 | dependency: "direct main" 146 | description: flutter 147 | source: sdk 148 | version: "0.0.0" 149 | flutter_bloc: 150 | dependency: "direct main" 151 | description: 152 | name: flutter_bloc 153 | url: "https://pub.dartlang.org" 154 | source: hosted 155 | version: "7.3.0" 156 | flutter_blurhash: 157 | dependency: transitive 158 | description: 159 | name: flutter_blurhash 160 | url: "https://pub.dartlang.org" 161 | source: hosted 162 | version: "0.6.0" 163 | flutter_cache_manager: 164 | dependency: transitive 165 | description: 166 | name: flutter_cache_manager 167 | url: "https://pub.dartlang.org" 168 | source: hosted 169 | version: "3.1.2" 170 | flutter_lints: 171 | dependency: "direct dev" 172 | description: 173 | name: flutter_lints 174 | url: "https://pub.dartlang.org" 175 | source: hosted 176 | version: "1.0.4" 177 | flutter_localizations: 178 | dependency: transitive 179 | description: flutter 180 | source: sdk 181 | version: "0.0.0" 182 | flutter_test: 183 | dependency: "direct dev" 184 | description: flutter 185 | source: sdk 186 | version: "0.0.0" 187 | flutter_web_plugins: 188 | dependency: transitive 189 | description: flutter 190 | source: sdk 191 | version: "0.0.0" 192 | get_it: 193 | dependency: "direct main" 194 | description: 195 | name: get_it 196 | url: "https://pub.dartlang.org" 197 | source: hosted 198 | version: "7.2.0" 199 | http: 200 | dependency: transitive 201 | description: 202 | name: http 203 | url: "https://pub.dartlang.org" 204 | source: hosted 205 | version: "0.13.3" 206 | http_parser: 207 | dependency: transitive 208 | description: 209 | name: http_parser 210 | url: "https://pub.dartlang.org" 211 | source: hosted 212 | version: "4.0.0" 213 | internet_connection_checker: 214 | dependency: transitive 215 | description: 216 | name: internet_connection_checker 217 | url: "https://pub.dartlang.org" 218 | source: hosted 219 | version: "0.0.1+2" 220 | intl: 221 | dependency: transitive 222 | description: 223 | name: intl 224 | url: "https://pub.dartlang.org" 225 | source: hosted 226 | version: "0.17.0" 227 | js: 228 | dependency: transitive 229 | description: 230 | name: js 231 | url: "https://pub.dartlang.org" 232 | source: hosted 233 | version: "0.6.3" 234 | lints: 235 | dependency: transitive 236 | description: 237 | name: lints 238 | url: "https://pub.dartlang.org" 239 | source: hosted 240 | version: "1.0.1" 241 | matcher: 242 | dependency: transitive 243 | description: 244 | name: matcher 245 | url: "https://pub.dartlang.org" 246 | source: hosted 247 | version: "0.12.10" 248 | meta: 249 | dependency: transitive 250 | description: 251 | name: meta 252 | url: "https://pub.dartlang.org" 253 | source: hosted 254 | version: "1.7.0" 255 | nested: 256 | dependency: transitive 257 | description: 258 | name: nested 259 | url: "https://pub.dartlang.org" 260 | source: hosted 261 | version: "1.0.0" 262 | octo_image: 263 | dependency: transitive 264 | description: 265 | name: octo_image 266 | url: "https://pub.dartlang.org" 267 | source: hosted 268 | version: "1.0.0+1" 269 | package_info: 270 | dependency: transitive 271 | description: 272 | name: package_info 273 | url: "https://pub.dartlang.org" 274 | source: hosted 275 | version: "2.0.2" 276 | path: 277 | dependency: transitive 278 | description: 279 | name: path 280 | url: "https://pub.dartlang.org" 281 | source: hosted 282 | version: "1.8.0" 283 | path_provider: 284 | dependency: transitive 285 | description: 286 | name: path_provider 287 | url: "https://pub.dartlang.org" 288 | source: hosted 289 | version: "2.0.5" 290 | path_provider_linux: 291 | dependency: transitive 292 | description: 293 | name: path_provider_linux 294 | url: "https://pub.dartlang.org" 295 | source: hosted 296 | version: "2.1.0" 297 | path_provider_macos: 298 | dependency: transitive 299 | description: 300 | name: path_provider_macos 301 | url: "https://pub.dartlang.org" 302 | source: hosted 303 | version: "2.0.2" 304 | path_provider_platform_interface: 305 | dependency: transitive 306 | description: 307 | name: path_provider_platform_interface 308 | url: "https://pub.dartlang.org" 309 | source: hosted 310 | version: "2.0.1" 311 | path_provider_windows: 312 | dependency: transitive 313 | description: 314 | name: path_provider_windows 315 | url: "https://pub.dartlang.org" 316 | source: hosted 317 | version: "2.0.3" 318 | pedantic: 319 | dependency: transitive 320 | description: 321 | name: pedantic 322 | url: "https://pub.dartlang.org" 323 | source: hosted 324 | version: "1.11.1" 325 | platform: 326 | dependency: transitive 327 | description: 328 | name: platform 329 | url: "https://pub.dartlang.org" 330 | source: hosted 331 | version: "3.0.2" 332 | plugin_platform_interface: 333 | dependency: transitive 334 | description: 335 | name: plugin_platform_interface 336 | url: "https://pub.dartlang.org" 337 | source: hosted 338 | version: "2.0.1" 339 | process: 340 | dependency: transitive 341 | description: 342 | name: process 343 | url: "https://pub.dartlang.org" 344 | source: hosted 345 | version: "4.2.3" 346 | provider: 347 | dependency: transitive 348 | description: 349 | name: provider 350 | url: "https://pub.dartlang.org" 351 | source: hosted 352 | version: "6.0.1" 353 | rxdart: 354 | dependency: transitive 355 | description: 356 | name: rxdart 357 | url: "https://pub.dartlang.org" 358 | source: hosted 359 | version: "0.27.2" 360 | shared_preferences: 361 | dependency: transitive 362 | description: 363 | name: shared_preferences 364 | url: "https://pub.dartlang.org" 365 | source: hosted 366 | version: "2.0.8" 367 | shared_preferences_linux: 368 | dependency: transitive 369 | description: 370 | name: shared_preferences_linux 371 | url: "https://pub.dartlang.org" 372 | source: hosted 373 | version: "2.0.2" 374 | shared_preferences_macos: 375 | dependency: transitive 376 | description: 377 | name: shared_preferences_macos 378 | url: "https://pub.dartlang.org" 379 | source: hosted 380 | version: "2.0.2" 381 | shared_preferences_platform_interface: 382 | dependency: transitive 383 | description: 384 | name: shared_preferences_platform_interface 385 | url: "https://pub.dartlang.org" 386 | source: hosted 387 | version: "2.0.0" 388 | shared_preferences_web: 389 | dependency: transitive 390 | description: 391 | name: shared_preferences_web 392 | url: "https://pub.dartlang.org" 393 | source: hosted 394 | version: "2.0.2" 395 | shared_preferences_windows: 396 | dependency: transitive 397 | description: 398 | name: shared_preferences_windows 399 | url: "https://pub.dartlang.org" 400 | source: hosted 401 | version: "2.0.2" 402 | sky_engine: 403 | dependency: transitive 404 | description: flutter 405 | source: sdk 406 | version: "0.0.99" 407 | source_span: 408 | dependency: transitive 409 | description: 410 | name: source_span 411 | url: "https://pub.dartlang.org" 412 | source: hosted 413 | version: "1.8.1" 414 | sqflite: 415 | dependency: transitive 416 | description: 417 | name: sqflite 418 | url: "https://pub.dartlang.org" 419 | source: hosted 420 | version: "2.0.0+4" 421 | sqflite_common: 422 | dependency: transitive 423 | description: 424 | name: sqflite_common 425 | url: "https://pub.dartlang.org" 426 | source: hosted 427 | version: "2.0.1+1" 428 | stack_trace: 429 | dependency: transitive 430 | description: 431 | name: stack_trace 432 | url: "https://pub.dartlang.org" 433 | source: hosted 434 | version: "1.10.0" 435 | stream_channel: 436 | dependency: transitive 437 | description: 438 | name: stream_channel 439 | url: "https://pub.dartlang.org" 440 | source: hosted 441 | version: "2.1.0" 442 | string_scanner: 443 | dependency: transitive 444 | description: 445 | name: string_scanner 446 | url: "https://pub.dartlang.org" 447 | source: hosted 448 | version: "1.1.0" 449 | synchronized: 450 | dependency: transitive 451 | description: 452 | name: synchronized 453 | url: "https://pub.dartlang.org" 454 | source: hosted 455 | version: "3.0.0" 456 | term_glyph: 457 | dependency: transitive 458 | description: 459 | name: term_glyph 460 | url: "https://pub.dartlang.org" 461 | source: hosted 462 | version: "1.2.0" 463 | test_api: 464 | dependency: transitive 465 | description: 466 | name: test_api 467 | url: "https://pub.dartlang.org" 468 | source: hosted 469 | version: "0.4.2" 470 | typed_data: 471 | dependency: transitive 472 | description: 473 | name: typed_data 474 | url: "https://pub.dartlang.org" 475 | source: hosted 476 | version: "1.3.0" 477 | utils: 478 | dependency: "direct main" 479 | description: 480 | path: "plugin/utils" 481 | relative: true 482 | source: path 483 | version: "0.0.1" 484 | uuid: 485 | dependency: transitive 486 | description: 487 | name: uuid 488 | url: "https://pub.dartlang.org" 489 | source: hosted 490 | version: "3.0.4" 491 | vector_math: 492 | dependency: transitive 493 | description: 494 | name: vector_math 495 | url: "https://pub.dartlang.org" 496 | source: hosted 497 | version: "2.1.0" 498 | win32: 499 | dependency: transitive 500 | description: 501 | name: win32 502 | url: "https://pub.dartlang.org" 503 | source: hosted 504 | version: "2.2.9" 505 | xdg_directories: 506 | dependency: transitive 507 | description: 508 | name: xdg_directories 509 | url: "https://pub.dartlang.org" 510 | source: hosted 511 | version: "0.2.0" 512 | sdks: 513 | dart: ">=2.14.0 <3.0.0" 514 | flutter: ">=2.5.0" 515 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_clean_architecture 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number 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.14.0 <3.0.0" 22 | flutter: ">=2.5.0" 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | 33 | 34 | cupertino_icons: 1.0.3 35 | 36 | get_it: 7.2.0 37 | flutter_bloc: 7.3.0 38 | equatable: 2.0.3 39 | utils: 40 | path: plugin/utils 41 | 42 | core: 43 | path: core 44 | data: 45 | path: data 46 | domain: 47 | path: domain 48 | 49 | dio: 4.0.0 50 | cached_network_image: 3.1.0 51 | 52 | dev_dependencies: 53 | flutter_test: 54 | sdk: flutter 55 | 56 | # The "flutter_lints" package below contains a set of recommended lints to 57 | # encourage good coding practices. The lint set provided by the package is 58 | # activated in the `analysis_options.yaml` file located at the root of your 59 | # package. See that file for information about deactivating specific lint 60 | # rules and activating additional ones. 61 | flutter_lints: ^1.0.0 62 | 63 | # For information on the generic Dart part of this file, see the 64 | # following page: https://dart.dev/tools/pub/pubspec 65 | 66 | # The following section is specific to Flutter. 67 | flutter: 68 | 69 | # The following line ensures that the Material Icons font is 70 | # included with your application, so that you can use the icons in 71 | # the material Icons class. 72 | uses-material-design: true 73 | 74 | # To add assets to your application, add an assets section, like this: 75 | # assets: 76 | # - images/a_dot_burr.jpeg 77 | # - images/a_dot_ham.jpeg 78 | 79 | # An image asset can refer to one or more resolution-specific "variants", see 80 | # https://flutter.dev/assets-and-images/#resolution-aware. 81 | 82 | # For details regarding adding assets from package dependencies, see 83 | # https://flutter.dev/assets-and-images/#from-packages 84 | 85 | # To add custom fonts to your application, add a fonts section here, 86 | # in this "flutter" section. Each entry in this list should have a 87 | # "family" key with the font family name, and a "fonts" key with a 88 | # list giving the asset and other descriptors for the font. For 89 | # example: 90 | # fonts: 91 | # - family: Schyler 92 | # fonts: 93 | # - asset: fonts/Schyler-Regular.ttf 94 | # - asset: fonts/Schyler-Italic.ttf 95 | # style: italic 96 | # - family: Trajan Pro 97 | # fonts: 98 | # - asset: fonts/TrajanPro.ttf 99 | # - asset: fonts/TrajanPro_Bold.ttf 100 | # weight: 700 101 | # 102 | # For details regarding fonts from package dependencies, 103 | # see https://flutter.dev/custom-fonts/#from-packages 104 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility 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 | import 'package:flutter_test/flutter_test.dart'; 8 | 9 | void main() { 10 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {}); 11 | } 12 | -------------------------------------------------------------------------------- /tool/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage: 4 | # flutter test --coverage --merge-coverage 5 | # sh coverage.sh core data domain presentation 6 | 7 | mkdir coverage 8 | touch coverage/lcov.base.info 9 | 10 | for module in "$@" 11 | do 12 | echo "testing $module..." 13 | cd "$module" || exit 14 | flutter test --coverage --coverage-path=coverage/"${module}".info 15 | var1=lib/ 16 | var2=${module}/lib/ 17 | 18 | sed -i "s@${var1}@${var2}@" coverage/"${module}".info 19 | cat coverage/"${module}".info >> ../coverage/lcov.base.info 20 | cd .. 21 | done -------------------------------------------------------------------------------- /tool/generate_asset_metadata.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script used to analyze bundled assets and generate a dart file which contains 4 | # the relevant metadata needed at runtime without forcing the application to 5 | # download the assets. 6 | # 7 | # Usage: 8 | # ./tool/generate_asset_metadata.sh > lib/screen/sub_category/mid_autumn/view/photo_editor/models/assets.g.dart 9 | 10 | set -e 11 | 12 | output_metadata () { 13 | width=$(sips -g pixelWidth $1 | tail -n1 | cut -d" " -f4) 14 | height=$(sips -g pixelHeight $1 | tail -n1 | cut -d" " -f4) 15 | filepath=$1 16 | name=$(basename "${filepath%.*}") 17 | 18 | echo "Asset(name: '$name', path: '$1', size: Size($width, $height));" 19 | } 20 | 21 | echo "// GENERATED CODE - DO NOT MODIFY BY HAND" 22 | echo "" 23 | echo "import 'package:flutter/widgets.dart';" 24 | echo "import 'asset.dart';" 25 | echo "" 26 | echo "class Assets {" 27 | 28 | sticker=images/mid_autumn/sticker/*.webp 29 | 30 | echo " static const sticker = [" 31 | for prop in $sticker 32 | do 33 | width=$(sips -g pixelWidth $prop | tail -n1 | cut -d" " -f4) 34 | height=$(sips -g pixelHeight $prop | tail -n1 | cut -d" " -f4) 35 | name=$(basename "${prop%.*}") 36 | echo " Asset( 37 | name: '$name', 38 | path: '$prop', 39 | size: Size($width, $height), 40 | )," 41 | done 42 | 43 | echo " ];" 44 | 45 | boxTmpBg=images/mid_autumn/box_tmp_bg/*.webp 46 | 47 | echo " static const boxTmpbg = [" 48 | for prop in $boxTmpBg 49 | do 50 | width=$(sips -g pixelWidth $prop | tail -n1 | cut -d" " -f4) 51 | height=$(sips -g pixelHeight $prop | tail -n1 | cut -d" " -f4) 52 | name=$(basename "${prop%.*}") 53 | echo " Asset( 54 | name: '$name', 55 | path: '$prop', 56 | size: Size($width, $height), 57 | )," 58 | done 59 | 60 | echo " ];" 61 | 62 | boxTmpX2=images/mid_autumn/box_tmp_x2/*.png 63 | 64 | echo " static const boxTmpX2 = [" 65 | for prop in $boxTmpX2 66 | do 67 | width=$(sips -g pixelWidth $prop | tail -n1 | cut -d" " -f4) 68 | height=$(sips -g pixelHeight $prop | tail -n1 | cut -d" " -f4) 69 | name=$(basename "${prop%.*}") 70 | echo " Asset( 71 | name: '$name', 72 | path: '$prop', 73 | size: Size($width, $height), 74 | )," 75 | done 76 | 77 | echo " ];" 78 | 79 | boxTmpX4=images/mid_autumn/box_tmp_x4/*.png 80 | 81 | echo " static const boxTmpX4 = [" 82 | for prop in $boxTmpX4 83 | do 84 | width=$(sips -g pixelWidth $prop | tail -n1 | cut -d" " -f4) 85 | height=$(sips -g pixelHeight $prop | tail -n1 | cut -d" " -f4) 86 | name=$(basename "${prop%.*}") 87 | echo " Asset( 88 | name: '$name', 89 | path: '$prop', 90 | size: Size($width, $height), 91 | )," 92 | done 93 | 94 | echo " ];" 95 | 96 | boxTmpX6=images/mid_autumn/box_tmp_x6/*.png 97 | 98 | echo " static const boxTmpX6 = [" 99 | for prop in $boxTmpX6 100 | do 101 | width=$(sips -g pixelWidth $prop | tail -n1 | cut -d" " -f4) 102 | height=$(sips -g pixelHeight $prop | tail -n1 | cut -d" " -f4) 103 | name=$(basename "${prop%.*}") 104 | echo " Asset( 105 | name: '$name', 106 | path: '$prop', 107 | size: Size($width, $height), 108 | )," 109 | done 110 | 111 | echo " ];" 112 | 113 | echo " static const props = [ 114 | ...sticker, 115 | ...boxTmpX2, 116 | ...boxTmpX4, 117 | ...boxTmpX6, 118 | ];" 119 | echo "}" --------------------------------------------------------------------------------