├── .editorconfig ├── .env ├── .gitignore ├── .metadata ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── README.md ├── README_ZH.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── fluttertemplate │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── animations │ ├── acqua_text_out_of_band.riv │ ├── loader.flr │ └── lottie │ │ ├── 18582-as-the-waters-rise.json │ │ └── 29056-nepenthe-illustration.json ├── audio │ ├── Beethoven__Moonlight_Sonata.mid │ ├── chenicheng.xml │ ├── tapbutton.mp3 │ └── viper.mp3 ├── images │ ├── back.png │ ├── back2.png │ ├── bian.png │ ├── ifredom.jpg │ ├── loginbg.png │ ├── tab_1.png │ ├── tab_1s.png │ ├── tab_2.png │ ├── tab_2s.png │ ├── tab_3.png │ ├── tab_3s.png │ ├── tab_4.png │ └── tab_4s.png ├── lang │ ├── en.json │ └── zh.json ├── sounds │ └── Piano.sf2 └── webview │ ├── demo │ ├── Beethoven_AnDieFerneGeliebte.xml │ ├── demo.css │ ├── favicon.ico │ ├── index.html │ ├── index.js │ ├── indexbake.html │ └── opensheetmusicdisplay.min.js │ └── dist │ ├── css │ ├── app.d40fc157.css │ └── chunk-904b882c.f05ad106.css │ ├── favicon.ico │ ├── index.html │ ├── js │ ├── app.11b6c626.js │ ├── chunk-904b882c.b0e0c1c6.js │ └── chunk-vendors.2b3a5ceb.js │ └── musicXML │ ├── Beethoven_AnDieFerneGeliebte.xml │ ├── Mozart_DasVeilchen.xml │ └── MuzioClementi_SonatinaOpus36No1_Part1.xml ├── copy-app.js ├── devtools_options.yaml ├── docs └── issuse.md ├── et --softq ├── flutter tips.md ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── core │ ├── app │ │ ├── app.bottomsheets.dart │ │ ├── app.dart │ │ ├── app.dialogs.dart │ │ ├── app.locator.dart │ │ ├── app.logger.dart │ │ └── app.router.dart │ ├── constants │ │ ├── animations.dart │ │ ├── app_theme.dart │ │ ├── component_state.dart │ │ ├── constants.dart │ │ └── formatter_text.dart │ ├── localization │ │ ├── localization.dart │ │ └── setup_local.dart │ ├── managers │ │ ├── lifecycle_manager.dart │ │ └── restart_manager.dart │ ├── model │ │ ├── app_models.dart │ │ ├── school │ │ │ ├── school.dart │ │ │ ├── school.g.dart │ │ │ ├── student.dart │ │ │ ├── student.g.dart │ │ │ ├── teacher.dart │ │ │ └── teacher.g.dart │ │ ├── serializers.dart │ │ ├── serializers.g.dart │ │ └── userinfo │ │ │ ├── user.dart │ │ │ ├── user.g.dart │ │ │ ├── user_local.dart │ │ │ └── user_local.g.dart │ ├── services │ │ ├── api │ │ │ ├── apicode │ │ │ │ ├── api.dart │ │ │ │ └── whiteList.dart │ │ │ ├── common │ │ │ │ ├── code.dart │ │ │ │ ├── exception_handle.dart │ │ │ │ └── result_data.dart │ │ │ ├── exceptions │ │ │ │ └── network_exception.dart │ │ │ ├── http_service.dart │ │ │ ├── http_service_impl.dart │ │ │ └── interceptors │ │ │ │ ├── api_interceptor.dart │ │ │ │ ├── error_interceptor.dart │ │ │ │ ├── header_interceptor.dart │ │ │ │ ├── log_interceptor.dart │ │ │ │ ├── net_cache.dart │ │ │ │ └── token_interceptor.dart │ │ ├── app_settings_service.dart │ │ ├── auth_service.dart │ │ ├── connectivity_service.dart │ │ ├── environment_service.dart │ │ ├── file_helper.dart │ │ ├── hardware_info_service.dart │ │ ├── hive │ │ │ ├── hive_service.dart │ │ │ └── hive_service_impl.dart │ │ ├── keyboard_service.dart │ │ ├── local_storage_service.dart │ │ ├── share_service.dart │ │ ├── stoppable_service.dart │ │ └── url_service.dart │ └── utils │ │ ├── common │ │ ├── color_utils.dart │ │ ├── network_utils.dart │ │ ├── text_utils.dart │ │ └── validators.dart │ │ └── res │ │ ├── assets.dart │ │ ├── colors.dart │ │ ├── dimens.dart │ │ ├── gaps.dart │ │ ├── local_storage_keys.dart │ │ ├── locale_keys.dart │ │ └── styles.dart ├── main.dart └── ui │ ├── common │ ├── app_colors.dart │ ├── app_strings.dart │ └── ui_helpers.dart │ ├── views │ ├── 404.dart │ ├── error_page.dart │ ├── home │ │ ├── first_view │ │ │ ├── first_view.dart │ │ │ └── title_view.dart │ │ ├── forth_view │ │ │ └── forth_view.dart │ │ ├── home_view │ │ │ ├── home.dart │ │ │ └── home_view_model.dart │ │ ├── second_view │ │ │ └── second_view.dart │ │ └── third_view │ │ │ └── third_view.dart │ ├── login │ │ ├── login_phone_view.dart │ │ ├── login_phone_view_model.dart │ │ ├── login_view.dart │ │ └── login_view_model.dart │ ├── product_detail │ │ └── product_detail_view.dart │ ├── register │ │ └── register_view.dart │ ├── root_component.dart │ ├── start_up │ │ ├── start_up_view.dart │ │ └── start_up_view_model.dart │ └── update │ │ ├── update_view.dart │ │ └── update_viewmodel.dart │ └── widgets │ ├── DoubleBackExitApp │ ├── DoubleBackExitApp.dart │ └── TipsScaleAnimated.dart │ ├── appbar │ └── custom_appbar.dart │ ├── bottombar │ ├── bottom_bar_view.dart │ └── tab_icon_data.dart │ ├── buttons │ ├── gradient_button.dart │ └── image_background_button.dart │ ├── custom │ ├── base_dialog_wrapper.dart │ ├── default_page_transition.dart │ ├── lazy_index_stack.dart │ ├── page_route_animation.dart │ ├── screen.dart │ └── ui_helpers.dart │ ├── dialogs │ ├── confirm_dialog.dart │ └── info_alert │ │ ├── info_alert_dialog.dart │ │ └── info_alert_dialog_model.dart │ ├── drawer │ ├── drawer_user_controller.dart │ ├── example.dart │ └── home_drawer.dart │ ├── loading │ └── loading_animation.dart │ ├── notice │ ├── notice_sheet.dart │ └── notice_sheet_model.dart │ ├── popup │ └── popup.dart │ ├── routeanimation │ └── page_route_anim.dart │ ├── skeleton │ └── skeleton.dart │ ├── slider │ ├── base_slider.dart │ └── demo.dart │ ├── switchanimation │ └── animated_provider.dart │ └── textfield │ └── text_field.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements └── RunnerTests │ └── RunnerTests.swift ├── package.json ├── pubspec.lock ├── pubspec.yaml ├── stacked.json ├── test ├── helpers │ ├── test_helpers.dart │ └── test_helpers.mocks.dart ├── viewmodels │ ├── home_viewmodel_test.dart │ ├── info_alert_dialog_model_test.dart │ └── notice_sheet_model_test.dart └── widget_test.dart ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = false 8 | insert_final_newline = false -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | AUTHOR=IFREDOM 2 | DEV=DEVPLONMENT -------------------------------------------------------------------------------- /.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 | /android/app/build 48 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "flutter-template-perfect", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "flutter-template-perfect (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | }, 18 | { 19 | "name": "flutter-template-perfect (release mode)", 20 | "request": "launch", 21 | "type": "dart", 22 | "flutterMode": "release" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "disabled" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # changelog(更新日志) 2 | 3 | ## [1.1.0] - 2024-06-13 4 | 5 | ### new feature 6 | 7 | - update flutter sdk to 3.22.0 8 | - update all package dependencies version 9 | 10 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | # 模板 2 | 3 | 这是一个 flutter 完整商用项目模板 4 | 5 | ## 开始 6 | 7 | ```bash 8 | # 开发 9 | > flutter run 10 | 11 | # 通过指定入口文件运行 12 | > flutter run --target ex/exMain.dart 13 | 14 | # 构建 15 | > flutter build apk 16 | ``` 17 | 18 | ### 核心功能 19 | 20 | - stacked (MVVM 架构) 21 | - route (路由)基于 `auto_route` ,由 **build_runner** 自动生成 , 22 | - service (服务)基于 `get_it, stacked_service`,内置十种基础懒加载服务 [NavigationService,DialogService,BottomSheetService,SnackbarService] [ConnectivityService,OpenLinkService,ShareService,FileService,LocalStorageService...] 23 | - model (模型)基于 `built_value` ,由 **build_runner** 自动生成 24 | - localization (国际化)基于 `flutter_localizations`,内置中英文 [en,zh],只需在 **assets/lang** 定义 25 | - common utils(常用函数)位于 utils,内置常见颜色,尺寸[Colour,Distance,validators] 26 | 27 | ### 1. [MVVM 架构](https://pub.flutter-io.cn/packages/stacked) 28 | 29 | - 一个文件管理 Ui eg: **XX_view.dart** 30 | - 一个文件管理数据 eg: **XX_view_model.dart** 31 | 32 | ### 2. [服务](https://pub.flutter-io.cn/packages/get_it) 33 | 34 | - NavigationService: 路由服务,控制路由跳转,数据传输,路由监听。路由由指令自动生成。 35 | - DialogService: 对话框服务,默认对话框,可定制 UI 36 | - BottomSheetService: 应用底部提示框服务,可定制 UI 37 | - SnackbarService: 应用顶部提示框服务,可定制 UI 38 | 39 | - ConnectivityService :连接服务。应用是否处于前台或缩放到后台。监听应用生命周期。使用包 `connectivity_plus` 40 | - OpenLinkService : 通过链接打开一个 App. 使用包 `url_launcher` 41 | - ShareService : 分享. 使用包 `share` 42 | - FileService : 文件上传下载保存服务. 使用包 `path_provider` 43 | - LocalStorageService : 本地存储服务. 使用包 `shared_preferences` 44 | - AppSettingsService : 打开 APP 设置服务. 使用包 `app_settings` 45 | - EnvironmentService : 开发/产品 环境服务. 使用包 `flutter_dotenv` 46 | - device_info_plus : 获取设备信息服务. 使用包 `device_info_plus` 47 | - KeyboardService : 获取键盘状态服务. 使用包 `flutter_keyboard_visibility` 48 | - HttpService : Http 接口请求服务. 使用包 `dio` 49 | - StoppableService : 停止服务. 对已启动得服务进行控制管理,停用已经启用得服务 50 | 51 | 62 | 63 | ## 自动生成 数据模型 64 | 65 | > 在 cmd 中,项目根目录下运行以下指令: 66 | 67 | ```bash 68 | > flutter packages pub run build_runner build --delete-conflicting-outputs 69 | 70 | # or(或者) 71 | 72 | > flutter packages pub run build_runner watch 73 | ``` 74 | 75 | ## 自动生成 路由,定位,日志模型 76 | 77 | > 在 cmd 中,项目根目录下运行以下指令: 78 | 79 | ```bash 80 | # 根据 core/app/app.dart 生成 路由,定位,日志模型文件 81 | # 生成结果位于 /core/app/ 82 | > flutter pub run build_runner build --delete-conflicting-outputs stacked generate 83 | ``` 84 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | analyzer: 11 | errors: 12 | avoid_print: ignore 13 | constant_identifier_names: ignore 14 | include: package:flutter_lints/flutter.yaml 15 | 16 | linter: 17 | # The lint rules applied to this project can be customized in the 18 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 19 | # included above or to enable additional rules. A list of all available lints 20 | # and their documentation is published at 21 | # https://dart-lang.github.io/linter/lints/index.html. 22 | # 23 | # Instead of disabling a lint rule for the entire project in the 24 | # section below, it can also be suppressed for a single line of code 25 | # or a specific dart file by using the `// ignore: name_of_lint` and 26 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 27 | # producing the lint. 28 | rules: 29 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 30 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 31 | 32 | # Additional information about this file can be found at 33 | # https://dart.dev/guides/language/analysis-options 34 | -------------------------------------------------------------------------------- /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 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file("local.properties") 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader("UTF-8") { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty("flutter.versionCode") 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = "1" 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty("flutter.versionName") 21 | if (flutterVersionName == null) { 22 | flutterVersionName = "1.0" 23 | } 24 | 25 | android { 26 | namespace = "com.example.fluttertemplate" 27 | compileSdk = flutter.compileSdkVersion 28 | ndkVersion = "25.2.9519653" 29 | // ndkVersion = flutter.ndkVersion 30 | 31 | compileOptions { 32 | sourceCompatibility = JavaVersion.VERSION_11 33 | targetCompatibility = JavaVersion.VERSION_11 34 | } 35 | 36 | defaultConfig { 37 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 38 | applicationId = "com.example.fluttertemplate" 39 | // You can update the following values to match your application needs. 40 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 41 | minSdk = flutter.minSdkVersion 42 | targetSdk = flutter.targetSdkVersion 43 | versionCode = flutterVersionCode.toInteger() 44 | versionName = flutterVersionName 45 | } 46 | 47 | buildTypes { 48 | release { 49 | // TODO: Add your own signing config for the release build. 50 | // Signing with the debug keys for now, so `flutter run --release` works. 51 | signingConfig = signingConfigs.debug 52 | } 53 | } 54 | } 55 | 56 | flutter { 57 | source = "../.." 58 | } 59 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/fluttertemplate/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.fluttertemplate 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | // maven { url 'https://maven.aliyun.com/repository/google' } 6 | // maven { url 'https://maven.aliyun.com/repository/jcenter' } 7 | // maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } 8 | 9 | } 10 | } 11 | 12 | rootProject.buildDir = "../build" 13 | subprojects { 14 | project.layout.buildDirectory = rootProject.layout.buildDirectory.dir(project.name) 15 | project.evaluationDependsOn(':app') 16 | } 17 | 18 | tasks.register("clean", Delete) { 19 | delete rootProject.buildDir 20 | } 21 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "7.3.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.7.10" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /assets/animations/acqua_text_out_of_band.riv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/animations/acqua_text_out_of_band.riv -------------------------------------------------------------------------------- /assets/animations/loader.flr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/animations/loader.flr -------------------------------------------------------------------------------- /assets/audio/Beethoven__Moonlight_Sonata.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/audio/Beethoven__Moonlight_Sonata.mid -------------------------------------------------------------------------------- /assets/audio/tapbutton.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/audio/tapbutton.mp3 -------------------------------------------------------------------------------- /assets/audio/viper.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/audio/viper.mp3 -------------------------------------------------------------------------------- /assets/images/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/images/back.png -------------------------------------------------------------------------------- /assets/images/back2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/images/back2.png -------------------------------------------------------------------------------- /assets/images/bian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/images/bian.png -------------------------------------------------------------------------------- /assets/images/ifredom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/images/ifredom.jpg -------------------------------------------------------------------------------- /assets/images/loginbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/images/loginbg.png -------------------------------------------------------------------------------- /assets/images/tab_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/images/tab_1.png -------------------------------------------------------------------------------- /assets/images/tab_1s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/images/tab_1s.png -------------------------------------------------------------------------------- /assets/images/tab_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/images/tab_2.png -------------------------------------------------------------------------------- /assets/images/tab_2s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/images/tab_2s.png -------------------------------------------------------------------------------- /assets/images/tab_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/images/tab_3.png -------------------------------------------------------------------------------- /assets/images/tab_3s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/images/tab_3s.png -------------------------------------------------------------------------------- /assets/images/tab_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/images/tab_4.png -------------------------------------------------------------------------------- /assets/images/tab_4s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/images/tab_4s.png -------------------------------------------------------------------------------- /assets/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-title": "Title", 3 | "confirm-dialog-title": "Tips", 4 | "home-view-title": "Home", 5 | "home-view-no-posts": "No posts available", 6 | "settings-view-title": "Settings", 7 | "settings-view-permissions": "Permissions", 8 | "settings-view-permissions-desc": "Open App Permissions", 9 | "settings-view-app-settings": "App Settings", 10 | "settings-view-app-settings-desc": "Open App Settings", 11 | "settings-view-notifications": "Notifications", 12 | "settings-view-notifications-desc": "Enable Notifications", 13 | "settings-view-location": "Current location", 14 | "settings-view-sign-out": "Sign out", 15 | "settings-view-sign-out-desc": "Sign out of the app", 16 | "settings-view-snack-bar": "Snack Bar", 17 | "settings-view-snack-bar-desc": "Show Snack Bar", 18 | "login-view-title": "Login", 19 | "login-button": "Login", 20 | "button-cancel": "Cancel", 21 | "button-confirm": "Confirm", 22 | "email-hint": "Email", 23 | "password-hint": "Password", 24 | "invalid-email": "Invalid email", 25 | "invalid-phone-number": "Invalid phone number", 26 | "invalid-zip-code": "Invalid zip code", 27 | "password-empty": "Password is required", 28 | "password-short": "Password must be more than 6 characters", 29 | "snackbar-message": "SnackBar message", 30 | "snackbar-action": "Action", 31 | "invalid-postal-idcard": "invalid postal idcard" 32 | } 33 | -------------------------------------------------------------------------------- /assets/lang/zh.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-title": "享弹", 3 | "confirm-dialog-title": "提示", 4 | "home-view-title": "首页", 5 | "home-view-no-posts": "没有更多数据", 6 | "settings-view-title": "Settings", 7 | "settings-view-permissions": "Permissions", 8 | "settings-view-permissions-desc": "Open App Permissions", 9 | "settings-view-app-settings": "App Settings", 10 | "settings-view-app-settings-desc": "Open App Settings", 11 | "settings-view-notifications": "Notifications", 12 | "settings-view-notifications-desc": "Enable Notifications", 13 | "settings-view-location": "Current location", 14 | "settings-view-sign-out": "Sign out", 15 | "settings-view-sign-out-desc": "Sign out of the app", 16 | "settings-view-snack-bar": "Snack Bar", 17 | "settings-view-snack-bar-desc": "Show Snack Bar", 18 | "login-view-title": "Login", 19 | "login-button": "登录", 20 | "button-cancel": "取消", 21 | "button-confirm": "确定", 22 | "email-hint": "Email", 23 | "password-hint": "密码", 24 | "invalid-email": "不可用 email", 25 | "invalid-phone-number": "手机号码不正确", 26 | "invalid-zip-code": "不正确得压缩码", 27 | "password-empty": "必须输入密码", 28 | "password-short": "密码不能少于6位数", 29 | "snackbar-message": "SnackBar 通知", 30 | "snackbar-action": "SnackBar 操作", 31 | "invalid-postal-idcard": "身份证号码不正确" 32 | } -------------------------------------------------------------------------------- /assets/sounds/Piano.sf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/sounds/Piano.sf2 -------------------------------------------------------------------------------- /assets/webview/demo/demo.css: -------------------------------------------------------------------------------- 1 | p { 2 | margin: 0px 8px 8px 8px; 3 | } 4 | 5 | /* the select widths are not used for some reason, even if we don't set the style in index.html */ 6 | select { 7 | width: 100px; 8 | margin: 2px 8px 8px 8px; 9 | } 10 | 11 | #selectSample { 12 | width: 320px; 13 | } 14 | 15 | #selectBounding { 16 | width: 80%; 17 | } 18 | 19 | .bignum { 20 | width: 1em; 21 | text-align: center; 22 | font-size: 4em; 23 | color: white; 24 | background-color: black; 25 | padding: 0.3em; 26 | border-right: 0.3em solid white; 27 | } 28 | 29 | #error-tr { 30 | background-color: red; 31 | text-align: center; 32 | } 33 | 34 | #error-td { 35 | padding: 1em; 36 | color: white; 37 | } 38 | 39 | table { 40 | width: 100%; 41 | } 42 | -------------------------------------------------------------------------------- /assets/webview/demo/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/webview/demo/favicon.ico -------------------------------------------------------------------------------- /assets/webview/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | jquery加载 demo 8 | 9 | 10 | 14 | 18 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /assets/webview/dist/css/app.d40fc157.css: -------------------------------------------------------------------------------- 1 | #app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;margin-top:60px} -------------------------------------------------------------------------------- /assets/webview/dist/css/chunk-904b882c.f05ad106.css: -------------------------------------------------------------------------------- 1 | .page-wrapper[data-v-a3065b40]{width:100%;height:auto} -------------------------------------------------------------------------------- /assets/webview/dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/assets/webview/dist/favicon.ico -------------------------------------------------------------------------------- /assets/webview/dist/index.html: -------------------------------------------------------------------------------- 1 | pianoapp
-------------------------------------------------------------------------------- /copy-app.js: -------------------------------------------------------------------------------- 1 | // test.js 2 | var { exec } = require("child_process"); 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | 6 | var targetPath = "build/app/outputs/apk/release"; 7 | var cli = "cd " + targetPath; 8 | var APP_NAME = "app-release.apk"; 9 | exec(cli, { encoding: "utf8" }, function (err, stdout, stderr) { 10 | if (err) { 11 | console.log(err); 12 | return; 13 | } 14 | copyFile(resolvePath(targetPath, APP_NAME), resolvePath("./", APP_NAME)); 15 | }); 16 | 17 | function resolvePath(filePath, fileName) { 18 | return path.resolve(filePath, fileName); 19 | } 20 | function copyFile(source, target) { 21 | fs.writeFileSync(target, fs.readFileSync(source)); 22 | } 23 | -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | description: This file stores settings for Dart & Flutter DevTools. 2 | documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states 3 | extensions: 4 | -------------------------------------------------------------------------------- /docs/issuse.md: -------------------------------------------------------------------------------- 1 | # issuse 2 | 3 | 4 | ## 1.One or more plugins require a higher Android NDK version. 5 | 6 | 7 | change: android/app/build.gradle 8 | 9 | ```bash 10 | android { 11 | # from here 12 | // ndkVersion = flutter.ndkVersion 13 | # change to 14 | ndkVersion = "25.2.9519653" 15 | 16 | ... 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /flutter tips.md: -------------------------------------------------------------------------------- 1 | # Flutter 支持通过自动选择 DPI 依赖资源来加载资产 2 | 3 | https://flutter.dev/docs/development/ui/assets-and-images#declaring-resolution-aware-image-assets 4 | 5 | ## 错误处理方式 6 | 7 | https://github.com/cfug/flutter.cn/blob/3ef3c8cfb6e69f5842c3a566931f514c0b945837/src/docs/testing/errors.md 8 | 9 | ## 错误解决 10 | 11 | 1. MissingPluginException (MissingPluginException(No implementation found for method check on channel plugins.flutter.io/connectivity)) => 解决办法:新建项目,旧代码移动到全新项目 12 | 13 | ## built_value 14 | 15 | https://stackoverflow.com/questions/64876381/how-to-build-a-list-using-built-value 16 | 17 | ## keymap 快捷方式 18 | 19 | win + E 打开资源管理器 20 | 21 | ## 经验 22 | 23 | BottomNavigationBar + PageView 比用 BottomNavigationBar + IndexedStack 好一点,不用一次性加载完所有 page 24 | 25 | 26 | 27 | ## andorid 问题集锦 28 | 29 | ## android studio plugin 下载很慢,总是超时 30 | 31 | 直接去[插件市场下载](https://plugins.jetbrains.com/),下载之后解压到 androidstudio 安装目录 plugins 文件夹下 32 | 33 | ## 编译出错怎么查看更多信息 34 | 35 | 安卓采用 Gradle 进行构建,所以当出错后我们自然使用 Gradle 查看更多信息,那么 Gradle 在哪里呢? 36 | 37 | ```bash 38 | // 在你的项目路径下的这里 39 | ./android/gradlew 40 | ``` 41 | 42 | ```bash 43 | PS >./android/gradlew compileDebug --stacktrace -info 44 | ``` 45 | 46 | ## Gradle 错误, 通过 AS 打开 flutter 项目中的 android 文件夹,在 AS 右上侧面有一个 gradle 菜单,点击 Toogle Offline Mode 47 | 48 | ### [布局约束](https://www.jianshu.com/p/b956b8a37012) 49 | 50 | 51 | ### “error: cannot find symbol” when building for Android in flutter 52 | 53 | > flutterEngine.getPlugins().add(new com.example.appsettings.AppSettingsPlugin()); 54 | https://stackoverflow.com/questions/66305553/error-cannot-find-symbol-when-building-for-android-in-flutter 55 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 Flutter 2 | import UIKit 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/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/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 | Fluttertemplate 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | fluttertemplate 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 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/core/app/app.bottomsheets.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ************************************************************************** 4 | // StackedBottomsheetGenerator 5 | // ************************************************************************** 6 | 7 | import 'package:stacked_services/stacked_services.dart'; 8 | 9 | import 'app.locator.dart'; 10 | import '../../ui/widgets/notice/notice_sheet.dart'; 11 | 12 | enum BottomSheetType { 13 | notice, 14 | } 15 | 16 | void setupBottomSheetUi() { 17 | final bottomsheetService = locator(); 18 | 19 | final Map builders = { 20 | BottomSheetType.notice: (context, request, completer) => 21 | NoticeSheet(request: request, completer: completer), 22 | }; 23 | 24 | bottomsheetService.setCustomSheetBuilders(builders); 25 | } 26 | -------------------------------------------------------------------------------- /lib/core/app/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:connectivity_plus/connectivity_plus.dart'; 2 | import 'package:fluttertemplate/core/services/auth_service.dart'; 3 | import 'package:fluttertemplate/core/services/connectivity_service.dart'; 4 | import 'package:fluttertemplate/core/services/file_helper.dart'; 5 | import 'package:fluttertemplate/core/services/hardware_info_service.dart'; 6 | import 'package:fluttertemplate/core/services/local_storage_service.dart'; 7 | import 'package:fluttertemplate/core/services/share_service.dart'; 8 | import 'package:fluttertemplate/core/services/url_service.dart'; 9 | import 'package:fluttertemplate/ui/views/home/home_view/home.dart'; 10 | import 'package:fluttertemplate/ui/views/login/login_phone_view.dart'; 11 | import 'package:fluttertemplate/ui/views/login/login_view.dart'; 12 | import 'package:fluttertemplate/ui/views/product_detail/product_detail_view.dart'; 13 | import 'package:fluttertemplate/ui/views/register/register_view.dart'; 14 | import 'package:fluttertemplate/ui/views/start_up/start_up_view.dart'; 15 | import 'package:fluttertemplate/ui/views/update/update_view.dart'; 16 | import 'package:fluttertemplate/ui/widgets/dialogs/info_alert/info_alert_dialog.dart'; 17 | import 'package:fluttertemplate/ui/widgets/notice/notice_sheet.dart'; 18 | import 'package:stacked/stacked_annotations.dart'; 19 | import 'package:stacked_services/stacked_services.dart'; 20 | // document: https://stacked.filledstacks.com/docs/in-depth/service-locator 21 | // example: https://github.com/Stacked-Org/stacked/blob/9f88465576f3e40c22c82da0500704919476a47a/example/router_example/lib/app/app.dart#L82 22 | 23 | // Using StackedApp for state management, generating routes and dependency injection. 24 | @StackedApp( 25 | routes: [ 26 | MaterialRoute(page: StartUpView, initial: true), 27 | MaterialRoute(page: UpdateView), 28 | MaterialRoute(page: HomeView), 29 | MaterialRoute(page: LoginView), 30 | MaterialRoute(page: LoginPhoneView), 31 | MaterialRoute(page: RegisterView), 32 | MaterialRoute(page: ProductDetailView), 33 | ], 34 | dependencies: [ 35 | // Lazy singletons 36 | LazySingleton(classType: NavigationService), 37 | LazySingleton(classType: DialogService), 38 | LazySingleton(classType: SnackbarService), 39 | LazySingleton(classType: Connectivity), 40 | 41 | LazySingleton(classType: ConnectivityService), 42 | LazySingleton(classType: OpenLinkService), 43 | LazySingleton(classType: ShareService), 44 | LazySingleton(classType: HardwareInfoService), 45 | LazySingleton(classType: FileServiceImpl), 46 | LazySingleton(classType: AuthService), 47 | // singletons 48 | 49 | // Presolve 50 | InitializableSingleton(classType: LocalStorageService), 51 | ], 52 | bottomsheets: [ 53 | StackedBottomsheet(classType: NoticeSheet), 54 | // @stacked-bottom-sheet 55 | ], 56 | dialogs: [ 57 | StackedDialog(classType: InfoAlertDialog), 58 | // @stacked-dialog 59 | ], 60 | logger: StackedLogger(), 61 | ) 62 | class App { 63 | /** This class has no puporse besides housing the annotation that generates the required functionality(除了附加注释之外,没有任何用途) **/ 64 | } 65 | -------------------------------------------------------------------------------- /lib/core/app/app.dialogs.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ************************************************************************** 4 | // StackedDialogGenerator 5 | // ************************************************************************** 6 | 7 | import 'package:stacked_services/stacked_services.dart'; 8 | 9 | import 'app.locator.dart'; 10 | import '../../ui/widgets/dialogs/info_alert/info_alert_dialog.dart'; 11 | 12 | enum DialogType { 13 | infoAlert, 14 | } 15 | 16 | void setupDialogUi() { 17 | final dialogService = locator(); 18 | 19 | final Map builders = { 20 | DialogType.infoAlert: (context, request, completer) => 21 | InfoAlertDialog(request: request, completer: completer), 22 | }; 23 | 24 | dialogService.registerCustomDialogBuilders(builders); 25 | } 26 | -------------------------------------------------------------------------------- /lib/core/app/app.locator.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ************************************************************************** 4 | // StackedLocatorGenerator 5 | // ************************************************************************** 6 | 7 | // ignore_for_file: public_member_api_docs, implementation_imports, depend_on_referenced_packages 8 | 9 | import 'package:connectivity_plus/connectivity_plus.dart'; 10 | import 'package:stacked_services/src/dialog/dialog_service.dart'; 11 | import 'package:stacked_services/src/navigation/navigation_service.dart'; 12 | import 'package:stacked_services/src/snackbar/snackbar_service.dart'; 13 | import 'package:stacked_shared/stacked_shared.dart'; 14 | 15 | import '../services/auth_service.dart'; 16 | import '../services/connectivity_service.dart'; 17 | import '../services/file_helper.dart'; 18 | import '../services/hardware_info_service.dart'; 19 | import '../services/local_storage_service.dart'; 20 | import '../services/share_service.dart'; 21 | import '../services/url_service.dart'; 22 | 23 | final locator = StackedLocator.instance; 24 | 25 | Future setupLocator({ 26 | String? environment, 27 | EnvironmentFilter? environmentFilter, 28 | }) async { 29 | // Register environments 30 | locator.registerEnvironment( 31 | environment: environment, environmentFilter: environmentFilter); 32 | 33 | // Register dependencies 34 | locator.registerLazySingleton(() => NavigationService()); 35 | locator.registerLazySingleton(() => DialogService()); 36 | locator.registerLazySingleton(() => SnackbarService()); 37 | locator.registerLazySingleton(() => Connectivity()); 38 | locator.registerLazySingleton(() => ConnectivityService()); 39 | locator.registerLazySingleton(() => OpenLinkService()); 40 | locator.registerLazySingleton(() => ShareService()); 41 | locator.registerLazySingleton(() => HardwareInfoService()); 42 | locator.registerLazySingleton(() => FileServiceImpl()); 43 | locator.registerLazySingleton(() => AuthService()); 44 | final localStorageService = LocalStorageService(); 45 | await localStorageService.init(); 46 | locator.registerSingleton(localStorageService); 47 | } 48 | -------------------------------------------------------------------------------- /lib/core/constants/animations.dart: -------------------------------------------------------------------------------- 1 | /// File Paths and constants for flare animations 2 | class Animations { 3 | Animations._(); 4 | 5 | static const loader = 'assets/animations/acqua_text_out_of_band.riv'; 6 | static const loader_name = 'Aura'; 7 | } 8 | -------------------------------------------------------------------------------- /lib/core/constants/component_state.dart: -------------------------------------------------------------------------------- 1 | /// 枚举: 支持的语言种类 2 | enum SupportLocale { FOLLOW_SYSTEM, SIMPLIFIED_CHINESE, TRADITIONAL_CHINESE_TW, TRADITIONAL_CHINESE_HK, ENGLISH } 3 | 4 | enum BackImageMode { light, black } 5 | 6 | enum ConnectivityStatus { Cellular, WiFi, Offline, Init } 7 | 8 | enum SexEnum { man, women } 9 | 10 | enum UserinfoType { username, sex, age, address, description } 11 | 12 | enum DialogType { 13 | Basic, 14 | Generic, 15 | CUSTOM_INPUT, 16 | CUSTOM_ARRAY_SIZE, 17 | ABOUT_APP, 18 | } 19 | -------------------------------------------------------------------------------- /lib/core/constants/constants.dart: -------------------------------------------------------------------------------- 1 | class Constants { 2 | ///请求地址 3 | static const String BASE_URL = 'https://easy-mock.com/mock/59f69a7f70d0b32d6619db4e/user'; 4 | 5 | /// 视频地址 6 | static const freeVideoUrl = 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4'; 7 | 8 | // 是否开启debug模式 9 | static const bool DEBUG = true; 10 | 11 | /// 是否开启http代理 12 | static const bool useProxy = false; 13 | 14 | /// 是否开启反向代理的IP/域名地址 (前置条件:开启代理) 15 | static const String proxyAddress = "192.168.2.201:9003"; 16 | } 17 | -------------------------------------------------------------------------------- /lib/core/constants/formatter_text.dart: -------------------------------------------------------------------------------- 1 | class FormatText { 2 | static String formatLevelcode(String level) { 3 | const List statusMap = [ 4 | { 5 | "value": [0, 9], 6 | "text": "初学乍练" 7 | }, 8 | { 9 | "value": [10, 29], 10 | "text": "登堂入室" 11 | }, 12 | { 13 | "value": [30, 59], 14 | "text": "游刃有余" 15 | }, 16 | { 17 | "value": [60, 99], 18 | "text": "炉火纯青" 19 | }, 20 | { 21 | "value": [100, 999999], 22 | "text": "才华横溢" 23 | }, 24 | ]; 25 | 26 | Map filterMap = statusMap.firstWhere( 27 | (item) => (int.parse(level) >= item["value"][0] && int.parse(level) <= item["value"][1]), 28 | orElse: () => {}); 29 | 30 | return filterMap["text"]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/core/localization/setup_local.dart: -------------------------------------------------------------------------------- 1 | import 'package:intl/intl.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_localizations/flutter_localizations.dart'; 4 | 5 | import 'localization.dart'; 6 | 7 | /// 支持的语言列表 8 | /// https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes 9 | const supportedLocalCodes = ['zh', 'en', 'he']; // 中文,英文,西班牙语 10 | 11 | final supportedLocales = supportedLocalCodes.map((code) => Locale.fromSubtags(languageCode: code)).toList(); 12 | 13 | /// A callback provided by [MaterialApp] that lets you 14 | /// specify which locales you plan to support by returning them. 15 | Locale? loadSupportedLocals(Locale? locale, Iterable supportedLocales) { 16 | if (locale == null) { 17 | Intl.defaultLocale = supportedLocales.first.languageCode; 18 | return supportedLocales.first; 19 | } 20 | 21 | for (final supportedLocale in supportedLocales) { 22 | if (supportedLocale.languageCode == locale.languageCode || supportedLocale.countryCode == locale.countryCode) { 23 | Intl.defaultLocale = supportedLocale.languageCode; 24 | return supportedLocale; 25 | } 26 | } 27 | 28 | Intl.defaultLocale = supportedLocales.first.languageCode; 29 | return supportedLocales.first; 30 | } 31 | 32 | /// Internationalized apps that require translations for one of the 33 | /// locales listed in [GlobalMaterialLocalizations] should specify 34 | /// this parameter and list the [supportedLocales] that the 35 | /// application can handle. 36 | List get localizationsDelegates { 37 | return [ 38 | const AppLocalizationsDelegate(), 39 | 40 | const FallbackCupertinoLocalizationsDelegate(), 41 | 42 | /// 初始化默认的 Material 组件本地化 43 | GlobalMaterialLocalizations.delegate, 44 | 45 | ///初始化默认的 通用 Widget 组件本地化 46 | GlobalWidgetsLocalizations.delegate, 47 | 48 | ///初始化默认的 Cupertino 组件本地化 49 | GlobalCupertinoLocalizations.delegate, 50 | ]; 51 | } 52 | -------------------------------------------------------------------------------- /lib/core/managers/lifecycle_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertemplate/core/app/app.locator.dart'; 3 | import 'package:fluttertemplate/core/app/app.logger.dart'; 4 | 5 | import '../services/connectivity_service.dart'; 6 | import '../services/stoppable_service.dart'; 7 | 8 | /// A manager to start/stop [StoppableService]s when the app goes/returns into/from the background 9 | class LifeCycleManager extends StatefulWidget { 10 | final Widget child; 11 | 12 | const LifeCycleManager({Key? key, required this.child}) : super(key: key); 13 | 14 | _LifeCycleManagerState createState() => _LifeCycleManagerState(); 15 | } 16 | 17 | class _LifeCycleManagerState extends State with WidgetsBindingObserver { 18 | final _log = getLogger('LifeCycleManager'); 19 | List servicesToManage = [ 20 | locator(), 21 | ]; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return widget.child; 26 | } 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | WidgetsBinding.instance.addObserver(this); 32 | } 33 | 34 | @override 35 | void dispose() { 36 | super.dispose(); 37 | WidgetsBinding.instance.removeObserver(this); 38 | } 39 | 40 | @override 41 | void didChangeAppLifecycleState(AppLifecycleState state) { 42 | super.didChangeAppLifecycleState(state); 43 | _log.i('App life cycle change to $state'); 44 | servicesToManage.forEach((service) { 45 | if (state == AppLifecycleState.resumed) { 46 | service.start(); 47 | } else { 48 | service.stop(); 49 | } 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/core/managers/restart_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | ///这个组件用来重新加载整个child Widget的。当我们需要重启APP的时候,可以使用这个方案 4 | ///https://stackoverflow.com/questions/50115311/flutter-how-to-force-an-application-restart-in-production-mode 5 | class RestartManager extends StatefulWidget { 6 | final Widget child; 7 | 8 | const RestartManager({super.key, required this.child}); 9 | 10 | static restartApp(BuildContext context) { 11 | // https://stackoverflow.com/questions/59448102/ancestorstateoftype-is-deprecated-use-findancestorstateoftype-instead 12 | 13 | final RestartWidgetState? state = context.findAncestorStateOfType(); 14 | 15 | state!.restartApp(); 16 | } 17 | 18 | @override 19 | RestartWidgetState createState() => RestartWidgetState(); 20 | } 21 | 22 | class RestartWidgetState extends State { 23 | Key key = UniqueKey(); 24 | 25 | void restartApp() { 26 | setState(() { 27 | key = UniqueKey(); 28 | }); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Container( 34 | key: key, 35 | child: widget.child, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/core/model/app_models.dart: -------------------------------------------------------------------------------- 1 | class Result { 2 | final F? failure; 3 | final S? success; 4 | 5 | Result({this.failure, this.success}); 6 | 7 | factory Result.failed(F fail) => Result(failure: fail, success: null); 8 | factory Result.success(S result) => Result(failure: null, success: result); 9 | 10 | bool isSuccess() { 11 | if (failure != null || success == null) return false; 12 | return true; 13 | } 14 | 15 | bool get isFailed => !isSuccess(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/core/model/school/school.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:built_collection/built_collection.dart'; 4 | import 'package:built_value/built_value.dart'; 5 | import 'package:built_value/serializer.dart'; 6 | import 'package:fluttertemplate/core/model/school/teacher.dart'; 7 | 8 | import '../serializers.dart'; 9 | 10 | part 'school.g.dart'; 11 | 12 | abstract class SchoolModel implements Built { 13 | String get name; 14 | 15 | BuiltList get teacher; 16 | SchoolModel._(); 17 | factory SchoolModel([void Function(SchoolModelBuilder) updates]) = _$SchoolModel; 18 | 19 | String toJson() { 20 | return json.encode(serializers.serializeWith(SchoolModel.serializer, this)); 21 | } 22 | 23 | Map toMap() { 24 | return serializers.serializeWith(SchoolModel.serializer, this) as Map; 25 | } 26 | 27 | static SchoolModel? fromJson(Map json) { 28 | return serializers.deserializeWith(SchoolModel.serializer, json); 29 | } 30 | 31 | static Serializer get serializer => _$schoolModelSerializer; 32 | } 33 | -------------------------------------------------------------------------------- /lib/core/model/school/student.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:built_collection/built_collection.dart'; 4 | import 'package:built_value/built_value.dart'; 5 | import 'package:built_value/serializer.dart'; 6 | 7 | import '../serializers.dart'; 8 | 9 | part 'student.g.dart'; 10 | 11 | abstract class StudentModel implements Built { 12 | String get name; 13 | 14 | int get level; 15 | 16 | BuiltList get students; 17 | 18 | StudentModel._(); 19 | factory StudentModel([void Function(StudentModelBuilder) updates]) = _$StudentModel; 20 | 21 | String toJson() { 22 | return json.encode(serializers.serializeWith(StudentModel.serializer, this)); 23 | } 24 | 25 | Map toMap() { 26 | return serializers.serializeWith(StudentModel.serializer, this) as Map; 27 | } 28 | 29 | static StudentModel? fromJson(Map json) { 30 | return serializers.deserializeWith(StudentModel.serializer, json); 31 | } 32 | 33 | static Serializer get serializer => _$studentModelSerializer; 34 | } 35 | -------------------------------------------------------------------------------- /lib/core/model/school/teacher.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:built_value/built_value.dart'; 4 | import 'package:built_value/serializer.dart'; 5 | 6 | import '../serializers.dart'; 7 | 8 | part 'teacher.g.dart'; 9 | 10 | abstract class TeacherModel implements Built { 11 | String get name; 12 | int get level; 13 | 14 | TeacherModel._(); 15 | factory TeacherModel([void Function(TeacherModelBuilder) updates]) = _$TeacherModel; 16 | 17 | String toJson() { 18 | return json.encode(serializers.serializeWith(TeacherModel.serializer, this)); 19 | } 20 | 21 | Map toMap() { 22 | return serializers.serializeWith(TeacherModel.serializer, this) as Map; 23 | } 24 | 25 | static TeacherModel? fromJson(Map json) { 26 | return serializers.deserializeWith(TeacherModel.serializer, json); 27 | } 28 | 29 | static Serializer get serializer => _$teacherModelSerializer; 30 | } 31 | -------------------------------------------------------------------------------- /lib/core/model/serializers.dart: -------------------------------------------------------------------------------- 1 | library serializers; 2 | 3 | import 'package:built_collection/built_collection.dart'; 4 | import 'package:built_value/iso_8601_date_time_serializer.dart'; 5 | import 'package:built_value/serializer.dart'; 6 | import 'package:built_value/standard_json_plugin.dart'; 7 | import 'package:fluttertemplate/core/model/school/school.dart'; 8 | import 'package:fluttertemplate/core/model/school/student.dart'; 9 | import 'package:fluttertemplate/core/model/school/teacher.dart'; 10 | 11 | import 'userinfo/user.dart'; 12 | 13 | part 'serializers.g.dart'; 14 | 15 | @SerializersFor([ 16 | User, 17 | SchoolModel, 18 | TeacherModel, 19 | StudentModel, 20 | ]) 21 | final Serializers serializers = (_$serializers.toBuilder() 22 | ..addPlugin(StandardJsonPlugin()) 23 | ..add(Iso8601DateTimeSerializer())) 24 | .build(); 25 | -------------------------------------------------------------------------------- /lib/core/model/serializers.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'serializers.dart'; 4 | 5 | // ************************************************************************** 6 | // BuiltValueGenerator 7 | // ************************************************************************** 8 | 9 | Serializers _$serializers = (new Serializers().toBuilder() 10 | ..add(SchoolModel.serializer) 11 | ..add(StudentModel.serializer) 12 | ..add(TeacherModel.serializer) 13 | ..add(User.serializer) 14 | ..addBuilderFactory( 15 | const FullType(BuiltList, const [const FullType(StudentModel)]), 16 | () => new ListBuilder()) 17 | ..addBuilderFactory( 18 | const FullType(BuiltList, const [const FullType(TeacherModel)]), 19 | () => new ListBuilder())) 20 | .build(); 21 | 22 | // ignore_for_file: deprecated_member_use_from_same_package,type=lint 23 | -------------------------------------------------------------------------------- /lib/core/model/userinfo/user.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:built_value/built_value.dart'; 4 | import 'package:built_value/serializer.dart'; 5 | import 'package:fluttertemplate/core/model/serializers.dart'; 6 | 7 | part 'user.g.dart'; 8 | 9 | abstract class User implements Built { 10 | String get id; 11 | 12 | String get token; 13 | 14 | String? get mobile; 15 | 16 | int? get age; 17 | 18 | String toJson() { 19 | return json.encode(serializers.serializeWith(User.serializer, this)); 20 | } 21 | 22 | Map toMap() { 23 | return serializers.serializeWith(User.serializer, this) as Map; 24 | } 25 | 26 | factory User.fromJson(String jsonString) { 27 | return serializers.deserializeWith( 28 | User.serializer, 29 | json.decode(jsonString), 30 | ) as User; 31 | } 32 | 33 | factory User.fromMap(Map map) { 34 | return serializers.deserializeWith( 35 | User.serializer, 36 | map, 37 | ) as User; 38 | } 39 | 40 | User._(); 41 | static Serializer get serializer => _$userSerializer; 42 | factory User([updates(UserBuilder b)]) = _$User; 43 | } 44 | -------------------------------------------------------------------------------- /lib/core/model/userinfo/user_local.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | import './user.dart'; 3 | 4 | part 'user_local.g.dart'; 5 | 6 | /// A Hive Database compatible UserLocal Model 7 | /// - @HiveType(typeId: unique) is necessary for each HiveObject 8 | /// - @HiveField(unique) is necessary if you need the object to persist 9 | @HiveType(typeId: 0) 10 | class UserLocal extends HiveObject { 11 | @HiveField(0) 12 | final String id; 13 | 14 | @HiveField(1) 15 | final String name; 16 | 17 | @HiveField(2) 18 | final String username; 19 | 20 | @HiveField(3) 21 | final String mobile; 22 | 23 | @HiveField(4) 24 | final String email; 25 | 26 | @HiveField(5) 27 | final String website; 28 | 29 | UserLocal({ 30 | this.id = '', 31 | this.name = '', 32 | this.username = '', 33 | this.mobile = '', 34 | this.email = '', 35 | this.website = '', 36 | }); 37 | 38 | factory UserLocal.fromUser(User user) { 39 | return UserLocal( 40 | id: user.id, 41 | mobile: user.mobile ?? '', 42 | ); 43 | } 44 | 45 | factory UserLocal.fromMap(Map map) { 46 | return UserLocal(id: map['id'], mobile: map['mobile']); 47 | } 48 | 49 | Map toMap() { 50 | final Map map = {}; 51 | map['id'] = id; 52 | map['mobile'] = mobile; 53 | return map; 54 | } 55 | 56 | @override 57 | int get hashCode => id.hashCode ^ mobile.hashCode; 58 | 59 | @override 60 | bool operator ==(Object other) => 61 | identical(this, other) || 62 | other is UserLocal && runtimeType == other.runtimeType && id == other.id && mobile == other.mobile; 63 | } 64 | -------------------------------------------------------------------------------- /lib/core/model/userinfo/user_local.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user_local.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class UserLocalAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 0; 12 | 13 | @override 14 | UserLocal read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return UserLocal( 20 | id: fields[0] as String, 21 | name: fields[1] as String, 22 | username: fields[2] as String, 23 | mobile: fields[3] as String, 24 | email: fields[4] as String, 25 | website: fields[5] as String, 26 | ); 27 | } 28 | 29 | @override 30 | void write(BinaryWriter writer, UserLocal obj) { 31 | writer 32 | ..writeByte(6) 33 | ..writeByte(0) 34 | ..write(obj.id) 35 | ..writeByte(1) 36 | ..write(obj.name) 37 | ..writeByte(2) 38 | ..write(obj.username) 39 | ..writeByte(3) 40 | ..write(obj.mobile) 41 | ..writeByte(4) 42 | ..write(obj.email) 43 | ..writeByte(5) 44 | ..write(obj.website); 45 | } 46 | 47 | @override 48 | int get hashCode => typeId.hashCode; 49 | 50 | @override 51 | bool operator ==(Object other) => 52 | identical(this, other) || 53 | other is UserLocalAdapter && 54 | runtimeType == other.runtimeType && 55 | typeId == other.typeId; 56 | } 57 | -------------------------------------------------------------------------------- /lib/core/services/api/apicode/api.dart: -------------------------------------------------------------------------------- 1 | /// 请求接口地址 2 | class ApiCode { 3 | /// SOCKET URL 4 | static const String SOCKET_URL = 'ws://test.constspace.com/webSocket'; 5 | 6 | /// 登录接口 7 | static const String SIGN_IN = '/login'; 8 | 9 | /// 获取验证码 10 | static const String GET_CODE = '/getCode'; 11 | 12 | /// 查询是否新用户 13 | static const String ISNEW_USER = '/checkIsNewUser'; 14 | 15 | /// 查询用户信息 16 | static const String CHECK_USERINFO = '/queryuser'; 17 | 18 | /// 修改用户信息 19 | static const String UPDATE_USERINFO = '/updateuser'; 20 | 21 | /// 修改用户密码 22 | static const String RESET_PASSWORD = '/resetPwd'; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /lib/core/services/api/apicode/whiteList.dart: -------------------------------------------------------------------------------- 1 | import 'api.dart'; 2 | 3 | /// 白名单,无需token验证 4 | class WhiteList { 5 | static List list = const [ 6 | ApiCode.SIGN_IN, 7 | ApiCode.GET_CODE, 8 | ]; 9 | } 10 | -------------------------------------------------------------------------------- /lib/core/services/api/common/code.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * 状态码 3 | */ 4 | class Code { 5 | // HTTP状态码 6 | static const int success = 200; 7 | static const int success_not_content = 204; 8 | static const int unauthorized = 401; 9 | static const int forbidden = 403; 10 | static const int not_found = 404; 11 | static const int request_timeout = 408; 12 | static const int unprocessable_entity = 422; 13 | static const int interranl_server_error = 500; 14 | static const int bad_gateway = 502; 15 | static const int service_unavailable = 503; 16 | static const int gateway_timeout = 504; 17 | 18 | // 约定错误码 19 | static const int unknown_code = 1000; // 未知错误 20 | static const int parse_error_code = 1001; // 数据解析异常 21 | static const int socket_error_code = 1002; // 连接失败 22 | static const int http_error_code = 1003; // 网络错误 23 | static const int send_timeout_code = 1004; // 请求超时 24 | static const int connect_timeout_code = 1005; // 连接超时 25 | static const int receive_timeout_code = 1006; // 响应超时 26 | static const int unknow_host_code = 1007; // 未知主机 27 | static const int cancel_error_code = 1008; // 取消请求 28 | static const int unknown_error_code = 9999; // 未知错误代码 29 | 30 | static const String net_error_message = "网络异常"; 31 | static const String socket_error_message = "Socket Exception,检查网络!"; 32 | static const String http_error_message = "服务器异常!"; 33 | static const String timeout_error_message = "连接超时!"; 34 | static const String cancel_error_message = "取消请求"; 35 | static const String unknown_error_message = "未知异常"; 36 | 37 | // 分发错误信息 38 | static errorHandleFunction(code, message, isNoTip) { 39 | if (isNoTip) { 40 | return message; 41 | } 42 | // eventBus.dispatch(new HttpErrorEvent(code, message)); 43 | return message; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/core/services/api/common/exception_handle.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart' show DioException, DioExceptionType; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:fluttertemplate/core/app/app.router.dart'; 6 | import 'package:fluttertemplate/core/app/app.locator.dart'; 7 | import 'package:fluttertemplate/core/services/local_storage_service.dart'; 8 | 9 | import 'package:fluttertemplate/core/utils/res/local_storage_keys.dart'; 10 | import 'package:fluttertoast/fluttertoast.dart'; 11 | import 'package:stacked_services/stacked_services.dart'; 12 | 13 | import 'code.dart'; 14 | import 'result_data.dart'; 15 | 16 | class ExceptionHandle { 17 | static Future handleDioException(DioException error) async { 18 | // http网络请求成功,服务器返回的信息数据 19 | if (error.type == DioExceptionType.unknown || error.type == DioExceptionType.badResponse) { 20 | dynamic e = error.error; 21 | 22 | if (e is SocketException) { 23 | return ResultData('Socket Exception,检查网络!', true, Code.socket_error_code); 24 | } 25 | if (e is HttpException) { 26 | return ResultData('服务器异常!', true, Code.http_error_code); 27 | } 28 | var res = error.response?.data; 29 | Fluttertoast.showToast(msg: res.msg.toString()); 30 | 31 | /// 后台接口,登录失效,跳转到登录 ,https://www.jianshu.com/p/bd6157914c2d 32 | if (res.code == 10000) { 33 | final _localStorageService = locator(); 34 | _localStorageService.set(StorageKeys.TOKEN_KEY, ""); 35 | await StackedService.navigatorKey!.currentState! 36 | .pushNamedAndRemoveUntil(Routes.loginView, ModalRoute.withName("/")); 37 | } 38 | 39 | return ResultData('服务器异常!', true, Code.http_error_code); 40 | } else if (error.type == DioExceptionType.connectionTimeout || 41 | error.type == DioExceptionType.sendTimeout || 42 | error.type == DioExceptionType.receiveTimeout) { 43 | return ResultData('连接超时!', false, Code.connect_timeout_code); 44 | } else if (error.type == DioExceptionType.cancel) { 45 | return ResultData('取消请求', false, Code.cancel_error_code); 46 | } else { 47 | return ResultData('未知异常', false, Code.unknown_error_code); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/core/services/api/common/result_data.dart: -------------------------------------------------------------------------------- 1 | // http网络请求 2 | class ResultData { 3 | dynamic data; // 请求结果:异常信息 (dioError or others error) 4 | bool result; // 请求结果:成功或者失败 (request: success or false) 5 | int code; // 状态码:自定义 (state code: my define) 6 | dynamic headers; 7 | 8 | ResultData(this.data, this.result, this.code, {this.headers}); 9 | 10 | ResultData.fromJson(Map json) 11 | : data = json['data'], 12 | result = json['result'], 13 | code = json['code'], 14 | headers = json['headers']; 15 | 16 | Map toJson() => { 17 | 'data': data, 18 | 'result': result, 19 | 'code': code, 20 | 'headers': headers, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /lib/core/services/api/exceptions/network_exception.dart: -------------------------------------------------------------------------------- 1 | class NetworkException implements Exception { 2 | final String message; 3 | 4 | const NetworkException(this.message); 5 | } 6 | -------------------------------------------------------------------------------- /lib/core/services/api/http_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart' show Options; 4 | 5 | /// 接口 6 | abstract class HttpService { 7 | /// Send POST request to endpoint/[route] and return the `response` 8 | /// 9 | Future request(String apiCode, Map params, {Map headers, Options options}); 10 | 11 | /// Download file from [fileUrl] and return the File 12 | /// - throws `NetworkException` if file download fails 13 | Future downloadFile(String fileUrl); 14 | 15 | void dispose(); 16 | } 17 | -------------------------------------------------------------------------------- /lib/core/services/api/interceptors/api_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart' show InterceptorsWrapper, RequestInterceptorHandler, RequestOptions; 2 | import 'package:fluttertemplate/core/app/app.locator.dart'; 3 | import 'package:fluttertemplate/core/services/local_storage_service.dart'; 4 | import 'package:fluttertemplate/core/utils/res/local_storage_keys.dart'; 5 | 6 | import '../apicode/whiteList.dart'; 7 | 8 | class ApiInterceptors extends InterceptorsWrapper { 9 | final _localStorageService = locator(); 10 | // 白名单 11 | // ignore: unused_element 12 | _getUnWhitelistToken(String apiCode) async { 13 | String token = _localStorageService.get(StorageKeys.TOKEN_KEY); 14 | if (WhiteList.list.contains(apiCode)) { 15 | token = StorageKeys.DEFAULT_TOKEN_KEY; 16 | } 17 | return token; 18 | } 19 | 20 | @override 21 | onRequest(RequestOptions options, RequestInterceptorHandler handler) async { 22 | // 请求前,对参数进行额外设置 23 | return super.onRequest(options, handler); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/core/services/api/interceptors/error_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:connectivity_plus/connectivity_plus.dart' show Connectivity, ConnectivityResult; 2 | import 'package:dio/dio.dart' show DioException, InterceptorsWrapper, RequestInterceptorHandler, RequestOptions; 3 | import 'package:fluttertemplate/core/app/app.logger.dart'; 4 | 5 | class ErrorInterceptors extends InterceptorsWrapper { 6 | final _log = getLogger('Api - Interceptor - ErrorInterceptors'); 7 | 8 | @override 9 | onRequest(RequestOptions options, RequestInterceptorHandler handler) async { 10 | var connectivityResult = await (Connectivity().checkConnectivity()); 11 | if (connectivityResult == ConnectivityResult.none) { 12 | String tips = "网络异常,检查你的网络"; 13 | _log.e(tips); 14 | handler.reject(DioException(requestOptions: options, error: tips)); 15 | } 16 | return super.onRequest(options, handler); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/core/services/api/interceptors/header_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart' show InterceptorsWrapper, RequestInterceptorHandler, RequestOptions; 2 | 3 | class HeaderInterceptors extends InterceptorsWrapper { 4 | @override 5 | onRequest(RequestOptions options, RequestInterceptorHandler handler) async { 6 | return super.onRequest(options, handler); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/core/services/api/interceptors/log_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart' 2 | show 3 | DioException, 4 | ErrorInterceptorHandler, 5 | InterceptorsWrapper, 6 | RequestInterceptorHandler, 7 | RequestOptions, 8 | Response, 9 | ResponseInterceptorHandler; 10 | import 'package:fluttertemplate/core/app/app.logger.dart'; 11 | import 'package:fluttertemplate/core/constants/constants.dart'; 12 | 13 | bool openDebug = Constants.DEBUG; 14 | 15 | class LogsInterceptors extends InterceptorsWrapper { 16 | final _log = getLogger('Api - LogsInterceptors'); 17 | 18 | @override 19 | onRequest(RequestOptions options, RequestInterceptorHandler handler) async { 20 | if (openDebug) { 21 | if (options.data != null) { 22 | _log.i('request params: ${options.data.toString()} \r\n'); 23 | } 24 | } 25 | return super.onRequest(options, handler); 26 | } 27 | 28 | @override 29 | onResponse(Response response, ResponseInterceptorHandler handler) async { 30 | if (openDebug) { 31 | _log.i('response: ${response.toString()} \r\n'); 32 | } 33 | return super.onResponse(response, handler); 34 | } 35 | 36 | @override 37 | onError(DioException err, ErrorInterceptorHandler handler) async { 38 | if (openDebug) { 39 | _log.e('request error info: ${err.response?.toString() ?? ""}'); 40 | } 41 | return super.onError(err, handler); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/core/services/api/interceptors/token_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart' show InterceptorsWrapper, RequestInterceptorHandler, RequestOptions; 2 | import 'package:fluttertemplate/core/app/app.locator.dart'; 3 | import 'package:fluttertemplate/core/services/local_storage_service.dart'; 4 | 5 | import 'package:fluttertemplate/core/utils/res/local_storage_keys.dart'; 6 | 7 | const _authHeaderToekn = 'token'; 8 | 9 | class TokenInterceptors extends InterceptorsWrapper { 10 | final _localStorageService = locator(); 11 | String _token = ''; 12 | 13 | @override 14 | onRequest(RequestOptions options, RequestInterceptorHandler handler) async { 15 | if (_token == '') { 16 | var authorizationCode = await getAuthorization(); 17 | if (authorizationCode != null) { 18 | _token = authorizationCode; 19 | } 20 | } 21 | options.headers[_authHeaderToekn] = _token; 22 | return super.onRequest(options, handler); 23 | } 24 | 25 | getAuthorization() async { 26 | String token = (await _localStorageService.get(StorageKeys.TOKEN_KEY)) ?? ""; 27 | String result = ''; 28 | 29 | if (token == '') { 30 | result = StorageKeys.DEFAULT_TOKEN_KEY; 31 | } else { 32 | this._token = token; 33 | result = token; 34 | } 35 | return result; 36 | } 37 | 38 | clearAuthorization() { 39 | this._token = ''; 40 | _localStorageService.remove(StorageKeys.TOKEN_KEY); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/core/services/app_settings_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:app_settings/app_settings.dart'; 2 | import 'package:fluttertemplate/core/app/app.logger.dart'; 3 | 4 | class AppSettingsService { 5 | final _log = getLogger('AppSettingsServiceImpl'); 6 | 7 | Future openAppSettings() { 8 | _log.t('openAppSettings'); 9 | return AppSettings.openAppSettings(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/core/services/environment_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluttertemplate/core/utils/res/local_storage_keys.dart'; 2 | import 'package:flutter_dotenv/flutter_dotenv.dart'; 3 | 4 | /// 读取 .env 文件值 5 | class EnvironmentService { 6 | String getValue(String key, {bool verbose = false}) { 7 | final value = dotenv.env[key] ?? StorageKeys.NoKey; 8 | if (verbose) print('key:$key value:$value'); 9 | return value; 10 | } 11 | 12 | static Future getInstance() async { 13 | print('Load environment'); 14 | await dotenv.load(fileName: ".env"); 15 | print('Environement loaded'); 16 | return Future.value(EnvironmentService()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/core/services/file_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:http_parser/http_parser.dart'; 5 | import 'package:mime/mime.dart'; 6 | import 'package:path_provider/path_provider.dart'; 7 | import 'package:path/path.dart'; 8 | 9 | abstract class FileService { 10 | Future getApplicationDocumentsDirectoryPath(); 11 | 12 | Future getFileFromUrl(String url); 13 | 14 | Future convertFileToMultipartFile(File file); 15 | } 16 | 17 | class FileServiceImpl implements FileService { 18 | @override 19 | Future getApplicationDocumentsDirectoryPath() async { 20 | final directory = await getApplicationDocumentsDirectory(); 21 | 22 | return directory.path; 23 | } 24 | 25 | @override 26 | Future getFileFromUrl(String url) async { 27 | final dir = await getApplicationDocumentsDirectoryPath(); 28 | final file = File('$dir/${basename(url)}'); 29 | 30 | return file; 31 | } 32 | 33 | @override 34 | Future convertFileToMultipartFile(File file) async { 35 | final fileBaseName = basename(file.path); 36 | final mimeType = lookupMimeType(fileBaseName) ?? ''; 37 | final contentType = MediaType.parse(mimeType); 38 | 39 | return MultipartFile.fromFileSync( 40 | file.path, 41 | filename: fileBaseName, 42 | contentType: contentType, 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/core/services/hardware_info_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:android_id/android_id.dart'; 4 | import 'package:device_info_plus/device_info_plus.dart'; 5 | import 'package:fluttertemplate/core/app/app.logger.dart'; 6 | 7 | /// Service that is responsible for getting hardware device info 8 | class HardwareInfoService { 9 | final _log = getLogger("HardwareInfoService"); 10 | String _operatingSystem = ''; 11 | String _device = ''; 12 | String _udid = ''; 13 | 14 | String get operatingSystem => _operatingSystem; 15 | 16 | String get device => _device; 17 | 18 | String get udid => _udid; 19 | 20 | Future init() async { 21 | final deviceInfo = DeviceInfoPlugin(); 22 | 23 | if (Platform.isIOS) { 24 | IosDeviceInfo iosInfo = await deviceInfo.iosInfo; 25 | _udid = iosInfo.identifierForVendor!; 26 | _operatingSystem = 'iOS'; 27 | _device = iosInfo.utsname.machine; 28 | } else if (Platform.isAndroid) { 29 | const _androidIdPlugin = AndroidId(); 30 | _udid = (await _androidIdPlugin.getId())!; 31 | 32 | AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; 33 | _operatingSystem = 'Android'; 34 | _device = androidInfo.model; 35 | } 36 | 37 | _log.i('udid: $_udid'); 38 | _log.i('operating_system: $_operatingSystem'); 39 | _log.i('device: $_device'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/core/services/hive/hive_service.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/lib/core/services/hive/hive_service.dart -------------------------------------------------------------------------------- /lib/core/services/hive/hive_service_impl.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/lib/core/services/hive/hive_service_impl.dart -------------------------------------------------------------------------------- /lib/core/services/keyboard_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | class KeyboardService with ListenableServiceMixin { 5 | ReactiveValue _isKeyboardVisible = ReactiveValue(false); 6 | bool get isKeyboardVisible => _isKeyboardVisible.value; 7 | 8 | KeyboardService() { 9 | listenToReactiveValues([_isKeyboardVisible]); 10 | KeyboardVisibilityController().onChange.listen((bool visible) { 11 | _isKeyboardVisible.value = visible; 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/core/services/local_storage_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | class LocalStorageService { 4 | static LocalStorageService? _instance; 5 | static SharedPreferences? _preferences; 6 | static const notifications_key = 'notifications_key'; 7 | 8 | static Future getInstance() async { 9 | _instance ??= LocalStorageService(); 10 | _preferences ??= await SharedPreferences.getInstance(); 11 | 12 | return Future.value(_instance); 13 | // return _instance!; 14 | } 15 | 16 | Future init() async => await getInstance(); 17 | 18 | bool get hasNotificationsEnabled => get(notifications_key) ?? false; 19 | set hasNotificationsEnabled(bool value) => set(notifications_key, value); 20 | 21 | void set(String key, dynamic content) async { 22 | if (T is String) { 23 | await _preferences!.setString(key, content); 24 | } 25 | if (T is bool) { 26 | await _preferences!.setBool(key, content); 27 | } 28 | if (T is int) { 29 | await _preferences!.setInt(key, content); 30 | } 31 | if (T is double) { 32 | await _preferences!.setDouble(key, content); 33 | } 34 | if (T is List) { 35 | await _preferences!.setStringList(key, content); 36 | } 37 | } 38 | 39 | // The return value is dynamic and can be null! 40 | // 注意:返回值是dynamic,允许为空! 41 | bool? getBool(String key) { 42 | return _preferences?.getBool(key); 43 | } 44 | 45 | String? getString(String key) { 46 | return _preferences?.getString(key); 47 | } 48 | 49 | double? getDouble(String key) { 50 | return _preferences?.getDouble(key); 51 | } 52 | 53 | dynamic get(String key) async { 54 | print(T); 55 | print(T is bool); 56 | print(T == bool); 57 | var value; 58 | if (T == String) { 59 | value = _preferences!.getString(key); 60 | } else if (T == bool) { 61 | value = _preferences!.getBool(key); 62 | print("值"); 63 | print(value); 64 | } else if (T == double) { 65 | value = _preferences!.getDouble(key); 66 | } else if (T == double) { 67 | value = _preferences!.getDouble(key); 68 | } else { 69 | value = _preferences!.get(key); 70 | } 71 | return value; 72 | } 73 | 74 | Future remove(String key) async { 75 | final value = await _preferences!.remove(key); 76 | return value; 77 | } 78 | 79 | clear() async { 80 | await _preferences!.clear(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/core/services/share_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:share_plus/share_plus.dart'; 2 | 3 | /// wrapper around [Share] package 4 | class ShareService { 5 | /// Fires platform's share inteface 6 | share(String text) async { 7 | await Share.share(text); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/core/services/stoppable_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Abstract class that defines that a stoppable service 4 | /// - Stoppable service must be able to start 5 | /// - Stoppable service must be able to stop 6 | abstract class StoppableService { 7 | bool _serviceStopped = false; 8 | bool get serviceStopped => _serviceStopped; 9 | 10 | @mustCallSuper 11 | void stop() { 12 | _serviceStopped = true; 13 | } 14 | 15 | @mustCallSuper 16 | void start() { 17 | _serviceStopped = false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/core/services/url_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:url_launcher/url_launcher.dart'; 2 | 3 | class OpenLinkService { 4 | /// Opens the given [url] 5 | /// 6 | /// Throws error if the link can't be opened 7 | Future openLink(String url) async { 8 | final Uri _url = Uri.parse(url); 9 | 10 | // 需要手动更改: https://pub.dev/packages/url_launcher 11 | // Check if the url can be launched 12 | // bool canLaunchUrl = await canLaunchUrl(url); 13 | // if (!canLaunchUrl) return Result.failed("Could not launch $url"); 14 | 15 | await launchUrl(_url); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/core/utils/common/color_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HexToColor extends Color { 4 | static int _getColorFromHex(String hexColor) { 5 | hexColor = hexColor.toUpperCase().replaceAll("#", ""); 6 | // 如果传入的十六进制颜色值不符合要求,返回默认值 7 | hexColor = "FF$hexColor"; 8 | return int.parse(hexColor, radix: 16); 9 | } 10 | 11 | HexToColor(final String hexColor) : super(_getColorFromHex(hexColor)); 12 | } 13 | 14 | /// use example: HexToColor("#333333") 15 | -------------------------------------------------------------------------------- /lib/core/utils/common/network_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' as convert; 2 | 3 | import 'package:dio/dio.dart'; 4 | 5 | void checkForNetworkExceptions(Response response) { 6 | if (response.statusCode != 200) { 7 | throw 'Failed to connect to internet'; 8 | } 9 | } 10 | 11 | void showLoadingProgress(received, total) { 12 | if (total != -1) { 13 | print('${(received / total * 100).toStringAsFixed(0)}%'); 14 | } 15 | } 16 | 17 | dynamic decodeResponseBodyToJson(String body) { 18 | try { 19 | final data = convert.jsonDecode(body); 20 | return data; 21 | } on FormatException catch (e) { 22 | print('Network Utils: Failed to decode response body ${e.message}'); 23 | throw e.message; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/core/utils/common/text_utils.dart: -------------------------------------------------------------------------------- 1 | /// TextUtil. 2 | class TextUtil { 3 | /// isEmpty 4 | static bool isEmpty(String text) { 5 | return text == '' || text.isEmpty; 6 | } 7 | 8 | /// 每隔 x位 加 pattern 9 | static String formatDigitPattern(String text, {int digit = 4, String pattern = ' '}) { 10 | text = text.replaceAllMapped(RegExp("(.{$digit})"), (Match match) { 11 | return "${match.group(0)}$pattern"; 12 | }); 13 | if (text.endsWith(pattern)) { 14 | text = text.substring(0, text.length - 1); 15 | } 16 | return text; 17 | } 18 | 19 | /// 每隔 x位 加 pattern, 从末尾开始 20 | static String formatDigitPatternEnd(String text, {int digit = 4, String pattern = ' '}) { 21 | String temp = reverse(text); 22 | temp = formatDigitPattern(temp, digit: 3, pattern: ','); 23 | temp = reverse(temp); 24 | return temp; 25 | } 26 | 27 | /// 每隔4位加空格 28 | static String formatSpace4(String text) { 29 | return formatDigitPattern(text); 30 | } 31 | 32 | /// 每隔3三位加逗号 33 | /// num 数字或数字字符串。int型。 34 | static String formatComma3(Object num) { 35 | return formatDigitPatternEnd(num.toString(), digit: 3, pattern: ','); 36 | } 37 | 38 | /// hideNumber 39 | static String hideNumber(String phoneNo, {int start = 3, int end = 7, String replacement = '****'}) { 40 | return phoneNo.replaceRange(start, end, replacement); 41 | } 42 | 43 | /// replace 44 | static String replace(String text, Pattern from, String replace) { 45 | return text.replaceAll(from, replace); 46 | } 47 | 48 | /// split 49 | static List split(String text, Pattern pattern, {List defValue = const []}) { 50 | List list = text.split(pattern); 51 | return list; 52 | } 53 | 54 | /// reverse 55 | static String reverse(String text) { 56 | if (isEmpty(text)) return ''; 57 | StringBuffer sb = StringBuffer(); 58 | for (int i = text.length - 1; i >= 0; i--) { 59 | sb.writeCharCode(text.codeUnitAt(i)); 60 | } 61 | return sb.toString(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/core/utils/common/validators.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluttertemplate/core/utils/res/locale_keys.dart'; 2 | 3 | /// 要使用 Validators验证类,使用 with 将其混入 4 | /// example: class LoginPhoneViewModel extends BaseViewModel with Validators {} 5 | 6 | mixin class Validators { 7 | /// 手机号验证 8 | final phoneNumberRegExp = RegExp(r'^((13[0-9])|(14[0-9])|(15[0-9])|(16[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))\d{8}$'); 9 | 10 | /// 邮箱验证 11 | final emailRegExp = RegExp( 12 | r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,253}[a-zA-Z0-9])?)*$"); 13 | 14 | /// 登录密码:6~16位数字和字符组合 15 | final passwordRegExp = RegExp(r"(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,16}$"); 16 | 17 | /// 身份证号码 18 | RegExp postalCode = RegExp(r'^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|[Xx])$'); 19 | 20 | String? validateEmail(String value) { 21 | if (!emailRegExp.hasMatch(value.trim())) { 22 | return LocalKeys.invalid_email; 23 | } 24 | return null; 25 | } 26 | 27 | String? validatePhoneNumber(String value) { 28 | if (phoneNumberRegExp.hasMatch(value.trim())) { 29 | return null; 30 | } else { 31 | return LocalKeys.invalid_phone_number; 32 | } 33 | } 34 | 35 | String? validatePassword(String value) { 36 | if (value.trim().isEmpty) { 37 | return LocalKeys.password_empty; 38 | } else if (!passwordRegExp.hasMatch(value.trim())) { 39 | return LocalKeys.password_invalid; 40 | } 41 | return null; 42 | } 43 | 44 | String? validatePostalcode(String value) { 45 | if (value.length != 18) { 46 | return LocalKeys.invalid_postal_idcard; // 位数不够 47 | } 48 | // 身份证号码正则 49 | RegExp postalCode = RegExp(r'^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|[Xx])$'); 50 | // 通过验证,说明格式正确,但仍需计算准确性 51 | if (!postalCode.hasMatch(value)) { 52 | return LocalKeys.invalid_postal_idcard; 53 | } 54 | //将前17位加权因子保存在数组里 55 | final List idCardList = ["7", "9", "10", "5", "8", "4", "2", "1", "6", "3", "7", "9", "10", "5", "8", "4", "2"]; 56 | //这是除以11后,可能产生的11位余数、验证码,也保存成数组 57 | final List idCardYArray = ['1', '0', '10', '9', '8', '7', '6', '5', '4', '3', '2']; 58 | // 前17位各自乖以加权因子后的总和 59 | int idCardWiSum = 0; 60 | 61 | for (int i = 0; i < 17; i++) { 62 | int subStrIndex = int.parse(value.substring(i, i + 1)); 63 | int idCardWiIndex = int.parse(idCardList[i]); 64 | idCardWiSum += subStrIndex * idCardWiIndex; 65 | } 66 | // 计算出校验码所在数组的位置 67 | int idCardMod = idCardWiSum % 11; 68 | // 得到最后一位号码 69 | String idCardLast = value.substring(17, 18); 70 | //如果等于2,则说明校验码是10,身份证号码最后一位应该是X 71 | if (idCardMod == 2) { 72 | if (idCardLast != 'x' && idCardLast != 'X') { 73 | return LocalKeys.invalid_postal_idcard; 74 | } 75 | } else { 76 | //用计算出的验证码与最后一位身份证号码匹配,如果一致,说明通过,否则是无效的身份证号码 77 | if (idCardLast != idCardYArray[idCardMod]) { 78 | return LocalKeys.invalid_postal_idcard; 79 | } 80 | } 81 | return null; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/core/utils/res/assets.dart: -------------------------------------------------------------------------------- 1 | class Assets { 2 | static const placeholderImage = "assets/images/placeholder.jpg"; 3 | } 4 | -------------------------------------------------------------------------------- /lib/core/utils/res/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Colours { 4 | static const Color app_main = Color(0xFF4688FA); 5 | static const Color bg_color = Color(0xfff1f1f1); 6 | 7 | static const Color textPrimary = Color(0xFF101010); 8 | static const Color textSecondary = Color(0xFF555555); 9 | static const Color text_dark = Color(0xFF333333); 10 | static const Color text_normal = Color(0xFF666666); 11 | static const Color text_gray = Color(0xFF999999); 12 | static const Color text_gray_c = Color(0xFFcccccc); 13 | static const Color text_white = Color(0x0099FF); 14 | static const Color bg_gray = Color(0xFFF6F6F6); 15 | static const Color line = Color(0xFFEEEEEE); 16 | static const Color order_line = Color(0xFFDDDDDD); 17 | static const Color text_red = Color(0xFFFF4759); 18 | 19 | static const Color login_text_disabled = Color(0xFFD4E2FA); 20 | static const Color login_button_disabled = Color(0xFF96BBFA); 21 | } 22 | -------------------------------------------------------------------------------- /lib/core/utils/res/dimens.dart: -------------------------------------------------------------------------------- 1 | class Dimens { 2 | // px 像素字体尺寸 3 | static const double font_sp10 = 10; 4 | static const double font_sp12 = 12; 5 | static const double font_sp13 = 13; 6 | static const double font_sp14 = 14; 7 | static const double font_sp15 = 15; 8 | static const double font_sp16 = 16; 9 | static const double font_sp18 = 18; 10 | static const double font_sp20 = 20; 11 | 12 | // px 像素距离 13 | static const double gap_dp5 = 5; 14 | static const double gap_dp10 = 10; 15 | static const double gap_dp12 = 12; 16 | static const double gap_dp14 = 14; 17 | static const double gap_dp15 = 15; 18 | static const double gap_dp16 = 16; 19 | static const double gap_dp20 = 20; 20 | static const double gap_dp22 = 22; 21 | static const double gap_dp28 = 28; 22 | static const double gap_dp30 = 30; 23 | static const double gap_dp32 = 32; 24 | static const double gap_dp50 = 50; 25 | static const double gap_dp100 = 100; 26 | static const double gap_dp120 = 120; 27 | static const double gap_dp200 = 200; 28 | static const double gap_dp250 = 250; 29 | } 30 | -------------------------------------------------------------------------------- /lib/core/utils/res/gaps.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | import 'colors.dart'; 4 | 5 | /// 间隔 6 | class Gaps { 7 | /// 水平间隔 8 | static const Widget hGap4 = SizedBox(width: 4); 9 | static const Widget hGap5 = SizedBox(width: 5); 10 | static const Widget hGap6 = SizedBox(width: 6); 11 | static const Widget hGap8 = SizedBox(width: 8); 12 | static const Widget hGap10 = SizedBox(width: 10); 13 | static const Widget hGap12 = SizedBox(width: 12); 14 | static const Widget hGap14 = SizedBox(width: 14); 15 | static const Widget hGap15 = SizedBox(width: 15); 16 | static const Widget hGap16 = SizedBox(width: 16); 17 | static const Widget hGap20 = SizedBox(width: 20); 18 | static const Widget hGap22 = SizedBox(width: 22); 19 | static const Widget hGap24 = SizedBox(width: 24); 20 | static const Widget hGap26 = SizedBox(width: 26); 21 | static const Widget hGap28 = SizedBox(width: 28); 22 | static const Widget hGap30 = SizedBox(width: 30); 23 | static const Widget hGap32 = SizedBox(width: 32); 24 | static const Widget hGap40 = SizedBox(width: 40); 25 | static const Widget hGap50 = SizedBox(width: 50); 26 | static const Widget hGap64 = SizedBox(width: 60); 27 | static const Widget hGap70 = SizedBox(width: 70); 28 | static const Widget hGap80 = SizedBox(width: 80); 29 | static const Widget hGap100 = SizedBox(width: 100); 30 | static const Widget hGap200 = SizedBox(width: 200); 31 | 32 | /// 垂直间隔 33 | static const Widget vGap4 = SizedBox(height: 4); 34 | static const Widget vGap5 = SizedBox(height: 5); 35 | static const Widget vGap8 = SizedBox(height: 8); 36 | static const Widget vGap10 = SizedBox(height: 10); 37 | static const Widget vGap12 = SizedBox(height: 12); 38 | static const Widget vGap15 = SizedBox(height: 15); 39 | static const Widget vGap16 = SizedBox(height: 16); 40 | static const Widget vGap20 = SizedBox(height: 20); 41 | static const Widget vGap26 = SizedBox(height: 26); 42 | static const Widget vGap27 = SizedBox(height: 27); 43 | static const Widget vGap30 = SizedBox(height: 30); 44 | static const Widget vGap40 = SizedBox(height: 40); 45 | static const Widget vGap50 = SizedBox(height: 50); 46 | static const Widget vGap60 = SizedBox(height: 60); 47 | static const Widget vGap70 = SizedBox(height: 70); 48 | static const Widget vGap80 = SizedBox(height: 80); 49 | static const Widget vGap200 = SizedBox(height: 200); 50 | 51 | static Widget line = const SizedBox( 52 | height: 0.6, 53 | width: double.infinity, 54 | child: DecoratedBox(decoration: BoxDecoration(color: Colours.line)), 55 | ); 56 | 57 | static Widget empty = const SizedBox(); 58 | } 59 | -------------------------------------------------------------------------------- /lib/core/utils/res/local_storage_keys.dart: -------------------------------------------------------------------------------- 1 | // 确保localStorage中得每一个Key值 唯一 2 | class StorageKeys { 3 | // [.env] keys 4 | static const String NoKey = "NO_KEY"; 5 | 6 | // LocalStorageKeys 7 | static const TOKEN_KEY = 'token'; 8 | static const USER_ID_KEY = 'userid'; 9 | static const DEFAULT_TOKEN_KEY = 'defaultToken'; 10 | static const HAS_LOGIN_KEY = 'haslogin'; 11 | static const THEME_KEY = 'theme'; 12 | static const USERINFO_KEY = 'userinfo'; 13 | static const USER_NAME_KEY = "username"; 14 | static const MOBILE_KEY = "mobile"; 15 | static const PW_KEY = "password"; 16 | } 17 | -------------------------------------------------------------------------------- /lib/core/utils/res/locale_keys.dart: -------------------------------------------------------------------------------- 1 | class LocalKeys { 2 | static const app_title = 'app-title'; 3 | 4 | static const confirm_dialog_title = 'confirm-dialog-title'; 5 | 6 | static const home_view_title = 'home-view-title'; 7 | static const home_view_no_posts = 'home-view-no-posts'; 8 | 9 | static const settings_view_title = 'settings-view-title'; 10 | static const settings_view_permissions = 'settings-view-permissions'; 11 | static const settings_view_permissions_desc = 12 | 'settings-view-permissions-desc'; 13 | static const settings_view_app_settings = 'settings-view-app-settings'; 14 | static const settings_view_app_settings_desc = 15 | 'settings-view-app-settings-desc'; 16 | static const settings_view_notifications = 'settings-view-notifications'; 17 | static const settings_view_notifications_desc = 18 | 'settings-view-notifications-desc'; 19 | static const settings_view_location = 'settings-view-location'; 20 | static const settings_view_sign_out = 'settings-view-sign-out'; 21 | static const settings_view_sign_out_desc = 'settings-view-sign-out-desc'; 22 | static const settings_view_snack_bar = 'settings-view-snack-bar'; 23 | static const settings_view_snack_bar_desc = 'settings-view-snack-bar-desc'; 24 | 25 | static const login_view_title = 'login-view-title'; 26 | static const login_button = 'login-button'; 27 | 28 | static const button_cancel = 'button-cancel'; 29 | static const button_confirm = 'button-confirm'; 30 | 31 | static const email_hint = 'email-hint'; 32 | static const password_hint = 'password-hint'; 33 | 34 | static const invalid_email = 'invalid-email'; 35 | static const invalid_phone_number = 'invalid_phone_number'; 36 | static const invalid_zip_code = 'invalid-zip-code'; 37 | static const password_empty = 'password-empty'; 38 | static const password_short = 'password-short'; 39 | static const password_invalid = 'password-invalid'; 40 | 41 | static const invalid_postal_idcard = 'invalid_postal-idcard'; 42 | 43 | static const snackbar_message = 'snackbar-message'; 44 | static const snackbar_action = 'snackbar-action'; 45 | } 46 | -------------------------------------------------------------------------------- /lib/core/utils/res/styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | import 'colors.dart'; 4 | import 'dimens.dart'; 5 | 6 | class TextStyles { 7 | static const TextStyle textMain12 = TextStyle( 8 | fontSize: Dimens.font_sp12, 9 | color: Colours.app_main, 10 | ); 11 | static const TextStyle textMain14 = TextStyle( 12 | fontSize: Dimens.font_sp14, 13 | color: Colours.app_main, 14 | ); 15 | static const TextStyle textNormal12 = TextStyle( 16 | fontSize: Dimens.font_sp12, 17 | color: Colours.text_normal, 18 | ); 19 | static const TextStyle textDark12 = TextStyle( 20 | fontSize: Dimens.font_sp12, 21 | color: Colours.text_dark, 22 | ); 23 | static const TextStyle textDark14 = TextStyle( 24 | fontSize: Dimens.font_sp14, 25 | color: Colours.text_dark, 26 | ); 27 | static const TextStyle textDark16 = TextStyle( 28 | fontSize: Dimens.font_sp16, 29 | color: Colours.text_dark, 30 | ); 31 | static const TextStyle textBoldDark14 = 32 | TextStyle(fontSize: Dimens.font_sp14, color: Colours.text_dark, fontWeight: FontWeight.bold); 33 | static const TextStyle textBoldDark16 = 34 | TextStyle(fontSize: Dimens.font_sp16, color: Colours.text_dark, fontWeight: FontWeight.bold); 35 | static const TextStyle textBoldDark18 = 36 | TextStyle(fontSize: Dimens.font_sp18, color: Colours.text_dark, fontWeight: FontWeight.bold); 37 | static const TextStyle textBoldDark24 = 38 | TextStyle(fontSize: 24.0, color: Colours.text_dark, fontWeight: FontWeight.bold); 39 | static const TextStyle textBoldDark26 = 40 | TextStyle(fontSize: 26.0, color: Colours.text_dark, fontWeight: FontWeight.bold); 41 | static const TextStyle textGray10 = TextStyle( 42 | fontSize: Dimens.font_sp10, 43 | color: Colours.text_gray, 44 | ); 45 | static const TextStyle textGray12 = TextStyle( 46 | fontSize: Dimens.font_sp12, 47 | color: Colours.text_gray, 48 | ); 49 | static const TextStyle textGray14 = TextStyle( 50 | fontSize: Dimens.font_sp14, 51 | color: Colours.text_gray, 52 | ); 53 | static const TextStyle textGray16 = TextStyle( 54 | fontSize: Dimens.font_sp16, 55 | color: Colours.text_gray, 56 | ); 57 | static const TextStyle textGrayC12 = TextStyle( 58 | fontSize: Dimens.font_sp12, 59 | color: Colours.text_gray_c, 60 | ); 61 | static const TextStyle textGrayC14 = TextStyle( 62 | fontSize: Dimens.font_sp14, 63 | color: Colours.text_gray_c, 64 | ); 65 | static const TextStyle textWhite14 = TextStyle( 66 | fontSize: Dimens.font_sp14, 67 | color: Colours.text_white, 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:fluttertemplate/ui/views/error_page.dart'; 7 | 8 | import './core/Constants/Constants.dart'; 9 | import 'core/app/app.locator.dart'; 10 | import 'ui/views/root_component.dart'; 11 | 12 | class App { 13 | static void run() async { 14 | FlutterError.onError = (FlutterErrorDetails details) { 15 | _reportError(details.exception, details.stack); 16 | }; 17 | defaultApp(); 18 | } 19 | 20 | static void defaultApp() { 21 | BindingBase.debugZoneErrorsAreFatal = true; 22 | 23 | runZonedGuarded>(() async { 24 | WidgetsFlutterBinding.ensureInitialized(); 25 | 26 | ErrorWidget.builder = (FlutterErrorDetails details) { 27 | if (Constants.DEBUG) { 28 | FlutterError.dumpErrorToConsole(details); 29 | } else { 30 | Zone.current.handleUncaughtError(details.exception, details.stack as StackTrace); 31 | } 32 | return ErrorPage(details); 33 | }; 34 | 35 | /// Start getit location service(启动GetIt定位服务) 36 | await setupLocator(); 37 | 38 | // Set full screen (设置全屏) 39 | await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual); 40 | 41 | /// root Widget 42 | runApp(const RootComponent()); 43 | }, (error, stack) => _reportError(error, stack)); 44 | } 45 | 46 | // Upload application exception information to the server! 47 | // 上传应用异常信息到日志服务器! 48 | static Future _reportError(dynamic error, dynamic stackTrace) async { 49 | if (Constants.DEBUG) { 50 | print('Development mode, do not send exceptions to the server. $stackTrace'); 51 | return; 52 | } 53 | print('Send exception information to the server ...'); 54 | } 55 | } 56 | 57 | void main() async { 58 | App.run(); 59 | } 60 | -------------------------------------------------------------------------------- /lib/ui/common/app_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const Color kcPrimaryColor = Color(0xFF9600FF); 4 | const Color kcPrimaryColorDark = Color(0xFF300151); 5 | const Color kcDarkGreyColor = Color(0xFF1A1B1E); 6 | const Color kcMediumGrey = Color(0xFF474A54); 7 | const Color kcLightGrey = Color.fromARGB(255, 187, 187, 187); 8 | const Color kcVeryLightGrey = Color(0xFFE3E3E3); 9 | const Color kcBackgroundColor = kcDarkGreyColor; 10 | -------------------------------------------------------------------------------- /lib/ui/common/app_strings.dart: -------------------------------------------------------------------------------- 1 | const String ksHomeBottomSheetTitle = 'Build Great Apps!'; 2 | const String ksHomeBottomSheetDescription = 3 | 'Stacked is built to help you build better apps. Give us a chance and we\'ll prove it to you. Check out stacked.filledstacks.com to learn more'; 4 | -------------------------------------------------------------------------------- /lib/ui/common/ui_helpers.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | const double _tinySize = 5.0; 6 | const double _smallSize = 10.0; 7 | const double _mediumSize = 25.0; 8 | const double _largeSize = 50.0; 9 | const double _massiveSize = 120.0; 10 | 11 | const Widget horizontalSpaceTiny = SizedBox(width: _tinySize); 12 | const Widget horizontalSpaceSmall = SizedBox(width: _smallSize); 13 | const Widget horizontalSpaceMedium = SizedBox(width: _mediumSize); 14 | const Widget horizontalSpaceLarge = SizedBox(width: _largeSize); 15 | 16 | const Widget verticalSpaceTiny = SizedBox(height: _tinySize); 17 | const Widget verticalSpaceSmall = SizedBox(height: _smallSize); 18 | const Widget verticalSpaceMedium = SizedBox(height: _mediumSize); 19 | const Widget verticalSpaceLarge = SizedBox(height: _largeSize); 20 | const Widget verticalSpaceMassive = SizedBox(height: _massiveSize); 21 | 22 | Widget spacedDivider = const Column( 23 | children: [ 24 | verticalSpaceMedium, 25 | Divider(color: Colors.blueGrey, height: 5.0), 26 | verticalSpaceMedium, 27 | ], 28 | ); 29 | 30 | Widget verticalSpace(double height) => SizedBox(height: height); 31 | 32 | double screenWidth(BuildContext context) => MediaQuery.of(context).size.width; 33 | double screenHeight(BuildContext context) => MediaQuery.of(context).size.height; 34 | 35 | double screenHeightFraction(BuildContext context, 36 | {int dividedBy = 1, double offsetBy = 0, double max = 3000}) => 37 | min((screenHeight(context) - offsetBy) / dividedBy, max); 38 | 39 | double screenWidthFraction(BuildContext context, 40 | {int dividedBy = 1, double offsetBy = 0, double max = 3000}) => 41 | min((screenWidth(context) - offsetBy) / dividedBy, max); 42 | 43 | double halfScreenWidth(BuildContext context) => 44 | screenWidthFraction(context, dividedBy: 2); 45 | 46 | double thirdScreenWidth(BuildContext context) => 47 | screenWidthFraction(context, dividedBy: 3); 48 | 49 | double quarterScreenWidth(BuildContext context) => 50 | screenWidthFraction(context, dividedBy: 4); 51 | 52 | double getResponsiveHorizontalSpaceMedium(BuildContext context) => 53 | screenWidthFraction(context, dividedBy: 10); 54 | double getResponsiveSmallFontSize(BuildContext context) => 55 | getResponsiveFontSize(context, fontSize: 14, max: 15); 56 | 57 | double getResponsiveMediumFontSize(BuildContext context) => 58 | getResponsiveFontSize(context, fontSize: 16, max: 17); 59 | 60 | double getResponsiveLargeFontSize(BuildContext context) => 61 | getResponsiveFontSize(context, fontSize: 21, max: 31); 62 | 63 | double getResponsiveExtraLargeFontSize(BuildContext context) => 64 | getResponsiveFontSize(context, fontSize: 25); 65 | 66 | double getResponsiveMassiveFontSize(BuildContext context) => 67 | getResponsiveFontSize(context, fontSize: 30); 68 | 69 | double getResponsiveFontSize(BuildContext context, 70 | {double? fontSize, double? max}) { 71 | max ??= 100; 72 | 73 | var responsiveSize = min( 74 | screenWidthFraction(context, dividedBy: 10) * ((fontSize ?? 100) / 100), 75 | max); 76 | 77 | return responsiveSize; 78 | } 79 | -------------------------------------------------------------------------------- /lib/ui/views/404.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class WidgetNotFound extends StatelessWidget { 4 | const WidgetNotFound({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Scaffold( 9 | appBar: AppBar( 10 | title: const Text("not found"), 11 | ), 12 | body: const Text('not found'), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/ui/views/home/forth_view/forth_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertemplate/ui/widgets/appbar/custom_appbar.dart'; 3 | 4 | class ForthScreen extends StatefulWidget { 5 | const ForthScreen({super.key}); 6 | 7 | @override 8 | ForthScreenState createState() => ForthScreenState(); 9 | } 10 | 11 | class ForthScreenState extends State with TickerProviderStateMixin { 12 | @override 13 | Widget build(BuildContext context) { 14 | return Scaffold( 15 | appBar: CustomAppbar(title: "ForthScreen"), 16 | backgroundColor: Colors.transparent, 17 | body: const Center(child: Text("ForthScreen")), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/ui/views/home/home_view/home_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluttertemplate/core/app/app.bottomsheets.dart'; 2 | import 'package:fluttertemplate/core/app/app.locator.dart'; 3 | import 'package:fluttertemplate/core/constants/component_state.dart'; 4 | import 'package:fluttertemplate/ui/common/app_strings.dart'; 5 | import 'package:stacked/stacked.dart'; 6 | import 'package:stacked_services/stacked_services.dart'; 7 | 8 | class HomeViewModel extends BaseViewModel { 9 | // 初始化,可以做点什么,比如fetch数据,用户信息 10 | Future onModelReady() async {} 11 | 12 | final _dialogService = locator(); 13 | 14 | String get counterLabel => 'Counter is: $_counter'; 15 | 16 | int _counter = 0; 17 | 18 | void incrementCounter() { 19 | _counter++; 20 | rebuildUi(); 21 | } 22 | 23 | void showDialog() { 24 | _dialogService.showCustomDialog( 25 | variant: DialogType.Basic, 26 | title: 'Stacked Rocks!', 27 | description: 'Give stacked $_counter stars on Github', 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/ui/views/home/second_view/second_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SecondScreen extends StatefulWidget { 4 | const SecondScreen({super.key}); 5 | 6 | @override 7 | SecondScreenState createState() => SecondScreenState(); 8 | } 9 | 10 | class SecondScreenState extends State { 11 | @override 12 | Widget build(BuildContext context) { 13 | return const SafeArea( 14 | child: Scaffold( 15 | body: Center(child: Text("SecondScreen")), 16 | ), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/ui/views/home/third_view/third_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertemplate/ui/widgets/appbar/custom_appbar.dart'; 3 | 4 | class ThirdScreen extends StatefulWidget { 5 | const ThirdScreen({super.key}); 6 | 7 | @override 8 | ThirdScreenState createState() => ThirdScreenState(); 9 | } 10 | 11 | class ThirdScreenState extends State { 12 | double get keyWidth => 80 + (80 * _widthRatio); 13 | final double _widthRatio = 0.0; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | appBar: CustomAppbar(title: "ThirdScreen"), 19 | body: buildContent(), 20 | ); 21 | } 22 | 23 | Widget buildContent() { 24 | return const Center(child: Text("ThirdScreen")); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/ui/views/login/login_phone_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluttertemplate/core/app/app.router.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | import 'package:stacked_services/stacked_services.dart'; 4 | import 'package:fluttertemplate/core/app/app.locator.dart'; 5 | import 'package:fluttertemplate/core/utils/common/validators.dart'; 6 | 7 | // ViewModelProvider应该使用得是 LoginViewModel中得数据 8 | class LoginPhoneViewModel extends BaseViewModel with Validators { 9 | final _navigationService = locator(); 10 | bool _isBusy = false; 11 | 12 | bool get isBusy => _isBusy; 13 | 14 | /// 初始化数据,比如网络请求 15 | Future init() async { 16 | setBusy(true); 17 | // 延时3s执行返回 18 | Future.delayed(Duration(seconds: 3), () { 19 | print('延时1s执行'); 20 | setBusy(false); 21 | }); 22 | } 23 | 24 | Future loginWithPassword(String mobile, String password) async { 25 | await _navigationService.replaceWith(Routes.homeView); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/ui/views/product_detail/product_detail_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertemplate/core/utils/common/color_utils.dart'; 3 | import 'package:fluttertemplate/ui/widgets/appbar/custom_appbar.dart'; 4 | 5 | class ProductDetailView extends StatefulWidget { 6 | final Map post; 7 | const ProductDetailView(this.post, {super.key}); 8 | @override 9 | ProductDetailViewState createState() => ProductDetailViewState(); 10 | } 11 | 12 | class ProductDetailViewState extends State with TickerProviderStateMixin { 13 | @override 14 | void initState() { 15 | super.initState(); 16 | } 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: CustomAppbar(title: '详情页面', showBackButton: true), 22 | body: buildMainListView(), 23 | ); 24 | } 25 | 26 | Widget buildMainListView() { 27 | return SizedBox( 28 | height: double.infinity, 29 | child: ListView.separated( 30 | itemCount: 20, 31 | scrollDirection: Axis.vertical, 32 | itemBuilder: (BuildContext context, int index) { 33 | return Container( 34 | padding: const EdgeInsets.all(20), 35 | color: HexToColor("#88ADA6"), 36 | child: Row( 37 | mainAxisSize: MainAxisSize.max, 38 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 39 | children: [ 40 | Text( 41 | widget.post["title"], 42 | maxLines: 5, 43 | style: TextStyle(color: HexToColor("#544a7d")), 44 | ), 45 | Text( 46 | widget.post["description"], 47 | maxLines: 5, 48 | style: TextStyle(color: HexToColor("#ffd452")), 49 | ), 50 | ], 51 | ), 52 | ); 53 | }, 54 | separatorBuilder: (context, index) { 55 | return const Divider( 56 | height: 1, 57 | ); 58 | }), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/ui/views/register/register_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class RegisterView extends StatefulWidget { 4 | const RegisterView({super.key}); 5 | 6 | @override 7 | RegisterViewState createState() => RegisterViewState(); 8 | } 9 | 10 | class RegisterViewState extends State { 11 | @override 12 | Widget build(BuildContext context) { 13 | return const Scaffold( 14 | body: Text("Register"), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/ui/views/root_component.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertemplate/core/app/app.router.dart'; 3 | import 'package:fluttertemplate/core/constants/app_theme.dart'; 4 | import 'package:fluttertemplate/core/localization/setup_local.dart'; 5 | import 'package:fluttertemplate/core/managers/lifecycle_manager.dart'; 6 | import 'package:fluttertemplate/core/managers/restart_manager.dart'; 7 | import 'package:get/get_navigation/get_navigation.dart'; 8 | import 'package:stacked_services/stacked_services.dart'; 9 | 10 | class RootComponent extends StatefulWidget { 11 | final bool web; 12 | const RootComponent({super.key, this.web = false}); 13 | 14 | @override 15 | RootComponentState createState() => RootComponentState(); 16 | } 17 | 18 | class RootComponentState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return RestartManager( 22 | child: LifeCycleManager( 23 | child: GetMaterialApp( 24 | debugShowCheckedModeBanner: false, 25 | localizationsDelegates: localizationsDelegates, 26 | localeResolutionCallback: loadSupportedLocals, 27 | supportedLocales: supportedLocales, 28 | locale: const Locale('zh', 'CN'), 29 | title: 'flutterApp', 30 | theme: AppTheme.themData, 31 | navigatorKey: StackedService.navigatorKey, 32 | navigatorObservers: [StackedService.routeObserver], 33 | onGenerateRoute: StackedRouter().onGenerateRoute, 34 | routingCallback: routingCallback, 35 | ), 36 | ), 37 | ); 38 | } 39 | 40 | routingCallback(Routing? routing) { 41 | if (routing!.current == Routes.homeView) { 42 | // Future.delayed(const Duration(seconds: 2), () async { 43 | // Get.snackbar("Tips", "You are on homeView route"); 44 | // }); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/ui/views/start_up/start_up_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'package:fluttertemplate/ui/views/start_up/start_up_view_model.dart'; 5 | import 'package:fluttertemplate/ui/widgets/loading/loading_animation.dart'; 6 | 7 | class StartUpView extends StatelessWidget { 8 | const StartUpView({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | // use package ScreenUtil, initial 13 | // ScreenUtil.init( 14 | // BoxConstraints( 15 | // maxWidth: MediaQuery.of(context).size.width, 16 | // maxHeight: MediaQuery.of(context).size.height, 17 | // ), 18 | /// Design draft size(设计稿尺寸) 19 | // designSize: Size(750, 1334), 20 | // orientation: Orientation.portrait, 21 | // ); 22 | return ViewModelBuilder.reactive( 23 | viewModelBuilder: () => StartUpViewModel(), 24 | onViewModelReady: (model) => model.onModelReady(), 25 | builder: (context, model, child) => const Scaffold( 26 | body: Center( 27 | child: LoadingAnimation(), 28 | ), 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/ui/views/start_up/start_up_view_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluttertemplate/core/app/app.router.dart'; 2 | import 'package:fluttertemplate/core/app/app.locator.dart'; 3 | import 'package:fluttertemplate/core/model/userinfo/user.dart'; 4 | import 'package:fluttertemplate/core/services/auth_service.dart'; 5 | import 'package:fluttertemplate/core/services/connectivity_service.dart'; 6 | import 'package:fluttertemplate/core/services/local_storage_service.dart'; 7 | 8 | import 'package:fluttertemplate/core/utils/res/local_storage_keys.dart'; 9 | import 'package:stacked/stacked.dart'; 10 | import 'package:stacked_services/stacked_services.dart'; 11 | 12 | class StartUpViewModel extends BaseViewModel { 13 | final _connectivityService = locator(); 14 | final _navigationService = locator(); 15 | final _authService = locator(); 16 | final _localStorageService = locator(); 17 | 18 | bool _isConnected = false; 19 | bool get isConnected => _isConnected; 20 | 21 | Future runStartupLogic({bool? connectivityPassed}) async { 22 | if (connectivityPassed == null) await checkConnectivity(); 23 | 24 | if (!_isConnected) { 25 | print("无法连接到网络, 请稍后尝试"); 26 | return; 27 | } 28 | 29 | bool forceUpdateRequired = await _authService.isUpdateRequired(); 30 | 31 | if (forceUpdateRequired && forceUpdateRequired) { 32 | print('app需要更新,前往更新页面'); 33 | _navigationService.replaceWith(Routes.updateView); 34 | return; 35 | } 36 | print('是否登录 ${_authService.hasLoggedInUser}'); 37 | final bool isLoggedIn = _authService.hasLoggedInUser; 38 | 39 | if (isLoggedIn) { 40 | String id = (await _localStorageService.get(StorageKeys.USER_ID_KEY)) ?? ""; 41 | var res = await _authService.fetchUserInfo(id); 42 | print("res"); 43 | print(res); 44 | if (res.data["code"] == 0) { 45 | User userinfo = User.fromMap(res.data["data"]); 46 | if (userinfo.age! > 18) { 47 | await _navigationService.replaceWith(Routes.registerView); 48 | } else { 49 | await _navigationService.replaceWith(Routes.homeView); 50 | } 51 | } 52 | } else { 53 | redirectToLogin(); 54 | } 55 | } 56 | 57 | Future handleStartUpLogic() async {} 58 | 59 | void redirectToLogin() async { 60 | _navigationService.replaceWith(Routes.loginView); 61 | } 62 | 63 | checkConnectivity() async { 64 | _isConnected = await _connectivityService.isConnected; 65 | notifyListeners(); 66 | } 67 | 68 | onModelReady() async { 69 | await checkConnectivity(); 70 | runStartupLogic(connectivityPassed: _isConnected); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/ui/views/update/update_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertemplate/ui/widgets/custom/screen.dart'; 3 | 4 | import 'update_viewmodel.dart'; 5 | 6 | class UpdateView extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | return ScreenBuilder( 10 | viewModel: UpdateViewModel(), 11 | builder: (context, uiHelpers, model) => Scaffold( 12 | appBar: AppBar(), 13 | body: Text("Update View"), 14 | )); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/ui/views/update/update_viewmodel.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class UpdateViewModel extends BaseViewModel {} 4 | -------------------------------------------------------------------------------- /lib/ui/widgets/DoubleBackExitApp/DoubleBackExitApp.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'TipsScaleAnimated.dart'; 6 | 7 | /// 二次返回退出APP组件 8 | class DoubleBackExitApp extends StatefulWidget { 9 | /// 提示文字 10 | final String title; 11 | 12 | /// 退出提示动画组件 13 | const DoubleBackExitApp({super.key, this.title = '再按一次退出'}); 14 | 15 | @override 16 | DoubleBackExitAppState createState() => DoubleBackExitAppState(); 17 | } 18 | 19 | class DoubleBackExitAppState extends State with SingleTickerProviderStateMixin { 20 | late Animation animation; // 动画对象 21 | late AnimationController controller; 22 | late DateTime _lastPressedAt; // 上次点击时间 23 | late Timer _tipsTimer; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | // 动画对象 29 | controller = AnimationController( 30 | duration: const Duration(milliseconds: 200), 31 | vsync: this, 32 | ); 33 | // 定义开始到结束的值 34 | animation = Tween(begin: 0.0, end: 1.0).animate(controller); 35 | } 36 | 37 | @override 38 | void dispose() { 39 | _tipsTimer.cancel(); 40 | controller.dispose(); 41 | super.dispose(); 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return PopScope( 47 | // listen to the back event 监听返回事件 48 | canPop: handleWillPop(), 49 | child: TipsScaleAnimated( 50 | animation: animation, 51 | child: Text( 52 | widget.title, 53 | style: TextStyle( 54 | fontSize: 15, 55 | color: Colors.white.withOpacity(0.8), 56 | ), 57 | textAlign: TextAlign.center, 58 | ), 59 | ), 60 | ); 61 | } 62 | 63 | // validates if the user wants to exit the app in 2 seconds 验证用户是否要退出APP,默认2秒 64 | bool handleWillPop() { 65 | if (DateTime.now().difference(_lastPressedAt) > const Duration(seconds: 2)) { 66 | //两次点击间隔超过2秒则重新计时 67 | _lastPressedAt = DateTime.now(); 68 | runTips(); 69 | return false; 70 | } 71 | return exit(0); 72 | } 73 | 74 | // 底部提示信息动画控制 75 | void runTips() { 76 | const oneSec = Duration(milliseconds: 1300); 77 | _tipsTimer = Timer(Duration.zero, () => {}); 78 | 79 | controller.reset(); // 重置动画 80 | controller.forward(); // 开始动画 81 | 82 | // 间隔一秒后执行 隐藏 83 | _tipsTimer = Timer(oneSec, () { 84 | controller.reset(); 85 | }); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/ui/widgets/DoubleBackExitApp/TipsScaleAnimated.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TipsScaleAnimated extends AnimatedWidget { 4 | final Widget child; 5 | final Animation animation; 6 | 7 | // 传入animation动画对象给 继承类中的listenable属性(代表监听此对象) 8 | TipsScaleAnimated({Key? key, required this.child, required this.animation}) : super(key: key, listenable: animation); 9 | 10 | Widget build(BuildContext context) { 11 | final Animation _animation = listenable as Animation; // 获取动画监听对象animation 12 | 13 | return AnimatedBuilder( 14 | animation: _animation, // 定义动画效果 15 | child: PhysicalModel( 16 | color: Colors.transparent, // 设置背景颜色 17 | borderRadius: BorderRadius.circular(15), // 定义裁剪圆度 18 | clipBehavior: Clip.antiAlias, // 常规,使用什么方式剪裁 19 | child: Container( 20 | padding: EdgeInsets.only(top: 6.0, bottom: 6.0, left: 14, right: 14), 21 | color: Color.fromRGBO(0, 0, 0, 0.6), 22 | child: child, 23 | ), 24 | ), 25 | // 指定动画效果 26 | builder: (BuildContext context, Widget? child) { 27 | return Transform.scale( 28 | origin: Offset(0, 50), // 缩放后的偏移x,y位置 29 | scale: _animation.value, 30 | alignment: Alignment.center, // 缩放后的相对定位位置 31 | child: child, 32 | ); 33 | }, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/ui/widgets/bottombar/tab_icon_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TabIconData { 4 | TabIconData( 5 | {this.imagePath = '', 6 | this.index = 0, 7 | this.selectedImagePath = '', 8 | this.isSelected = false, 9 | this.animationController}); 10 | 11 | String imagePath; 12 | String selectedImagePath; 13 | bool isSelected; 14 | int index; 15 | 16 | AnimationController? animationController; 17 | 18 | static List tabIconsList = [ 19 | TabIconData( 20 | imagePath: 'assets/images/tab_1.png', 21 | selectedImagePath: 'assets/images/tab_1s.png', 22 | index: 0, 23 | isSelected: true, 24 | // animationController: animationController, 25 | ), 26 | TabIconData( 27 | imagePath: 'assets/images/tab_2.png', 28 | selectedImagePath: 'assets/images/tab_2s.png', 29 | index: 1, 30 | isSelected: false, 31 | // animationController: null, 32 | ), 33 | TabIconData( 34 | imagePath: 'assets/images/tab_3.png', 35 | selectedImagePath: 'assets/images/tab_3s.png', 36 | index: 2, 37 | isSelected: false, 38 | // animationController: null, 39 | ), 40 | TabIconData( 41 | imagePath: 'assets/images/tab_4.png', 42 | selectedImagePath: 'assets/images/tab_4s.png', 43 | index: 3, 44 | isSelected: false, 45 | // animationController: null, 46 | ), 47 | ]; 48 | } 49 | -------------------------------------------------------------------------------- /lib/ui/widgets/buttons/image_background_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | //自定义按钮 4 | class ImageBackgroundButton extends StatelessWidget { 5 | ImageBackgroundButton({ 6 | required this.image, 7 | required this.text, 8 | this.width, 9 | this.height, 10 | this.style, 11 | this.onPressed, 12 | this.borderRadius, 13 | }); 14 | 15 | // 渐变色数组 16 | final ImageProvider image; 17 | 18 | // 按钮宽高 19 | final double? width; 20 | final double? height; 21 | 22 | final String text; 23 | final TextStyle? style; 24 | final BorderRadius? borderRadius; 25 | 26 | //点击回调 27 | final GestureTapCallback? onPressed; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Container( 32 | width: this.width ?? 82, 33 | height: this.height ?? 26, 34 | decoration: BoxDecoration( 35 | image: DecorationImage( 36 | fit: BoxFit.fitWidth, 37 | image: this.image, 38 | ), 39 | borderRadius: borderRadius ?? BorderRadius.circular(25), 40 | ), 41 | child: ElevatedButton( 42 | // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)), 43 | // color: Colors.transparent, // 设为透明色 44 | // elevation: 0, // 正常时阴影隐藏 45 | // highlightElevation: 0, // 点击时阴影隐藏 46 | onPressed: () { 47 | onPressed!(); 48 | }, 49 | child: Container( 50 | alignment: Alignment.center, 51 | child: Text( 52 | text, 53 | style: style ?? 54 | TextStyle( 55 | color: Colors.white, 56 | fontSize: 15, 57 | ), 58 | ), 59 | ), 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/ui/widgets/custom/default_page_transition.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | class DefaultPageTransition extends PageRouteBuilder { 4 | final Widget child; 5 | 6 | DefaultPageTransition({required this.child}) 7 | : super(pageBuilder: (context, animation, animation2) { 8 | return child; 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /lib/ui/widgets/custom/lazy_index_stack.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | class LazyIndexedStack extends StatefulWidget { 4 | final AlignmentGeometry alignment; 5 | final TextDirection textDirection; 6 | final StackFit sizing; 7 | 8 | /// Current index of the stack 9 | final int index; 10 | 11 | /// number of views 12 | final int itemCount; 13 | 14 | /// Builder for the current index 15 | final IndexedWidgetBuilder itemBuilder; 16 | 17 | /// Reuse the created view 18 | final bool reuse; 19 | 20 | const LazyIndexedStack({ 21 | super.key, 22 | this.alignment = AlignmentDirectional.topStart, 23 | this.textDirection = TextDirection.ltr, 24 | this.sizing = StackFit.loose, 25 | this.index = 1, 26 | this.reuse = true, 27 | required this.itemBuilder, 28 | this.itemCount = 0, 29 | }); 30 | 31 | @override 32 | LazyIndexedStackState createState() => LazyIndexedStackState(); 33 | } 34 | 35 | class LazyIndexedStackState extends State { 36 | final List _children = []; 37 | final List _loaded = []; 38 | 39 | @override 40 | void initState() { 41 | for (int i = 0; i < widget.itemCount; ++i) { 42 | if (i == widget.index) { 43 | _children.add(widget.itemBuilder(context, i)); 44 | _loaded.add(true); 45 | } else { 46 | _children.add(Container()); 47 | _loaded.add(false); 48 | } 49 | } 50 | super.initState(); 51 | } 52 | 53 | @override 54 | void didUpdateWidget(LazyIndexedStack oldWidget) { 55 | for (int i = 0; i < widget.itemCount; ++i) { 56 | if (i == widget.index) { 57 | if (!_loaded[i]) { 58 | _children[i] = widget.itemBuilder(context, i); 59 | _loaded[i] = true; 60 | } else { 61 | if (widget.reuse) { 62 | return; 63 | } 64 | _children[i] = widget.itemBuilder(context, i); 65 | } 66 | } 67 | } 68 | 69 | super.didUpdateWidget(oldWidget); 70 | } 71 | 72 | @override 73 | Widget build(BuildContext context) { 74 | return IndexedStack( 75 | index: widget.index, 76 | alignment: widget.alignment, 77 | textDirection: widget.textDirection, 78 | sizing: widget.sizing, 79 | children: _children, 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/ui/widgets/custom/page_route_animation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class NoAnimRouteBuilder extends PageRouteBuilder { 4 | final Widget page; 5 | 6 | NoAnimRouteBuilder(this.page) 7 | : super( 8 | opaque: false, 9 | pageBuilder: (context, animation, secondaryAnimation) => page, 10 | transitionDuration: Duration(milliseconds: 0), 11 | transitionsBuilder: 12 | (context, animation, secondaryAnimation, child) => child); 13 | } 14 | 15 | class FadeRouteBuilder extends PageRouteBuilder { 16 | final Widget page; 17 | 18 | FadeRouteBuilder(this.page) 19 | : super( 20 | pageBuilder: (context, animation, secondaryAnimation) => page, 21 | transitionDuration: Duration(milliseconds: 500), 22 | transitionsBuilder: (context, animation, secondaryAnimation, 23 | child) => 24 | FadeTransition( 25 | opacity: Tween(begin: 0.1, end: 1.0).animate(CurvedAnimation( 26 | parent: animation, 27 | curve: Curves.fastOutSlowIn, 28 | )), 29 | child: child, 30 | )); 31 | } 32 | 33 | class SlideTopRouteBuilder extends PageRouteBuilder { 34 | final Widget page; 35 | 36 | SlideTopRouteBuilder(this.page) 37 | : super( 38 | pageBuilder: (context, animation, secondaryAnimation) => page, 39 | transitionDuration: Duration(milliseconds: 800), 40 | transitionsBuilder: 41 | (context, animation, secondaryAnimation, child) => 42 | SlideTransition( 43 | position: Tween( 44 | begin: Offset(0.0, -1.0), end: Offset(0.0, 0.0)) 45 | .animate(CurvedAnimation( 46 | parent: animation, curve: Curves.fastOutSlowIn)), 47 | child: child, 48 | )); 49 | } 50 | 51 | class SizeRoute extends PageRouteBuilder { 52 | final Widget page; 53 | 54 | SizeRoute(this.page) 55 | : super( 56 | pageBuilder: (context, animation, secondaryAnimation) => page, 57 | transitionDuration: Duration(milliseconds: 300), 58 | transitionsBuilder: (context, animation, secondaryAnimation, child) => 59 | // Align( 60 | // child: SizeTransition(child: child, sizeFactor: animation), 61 | // ), 62 | ScaleTransition( 63 | scale: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( 64 | parent: animation, curve: Curves.fastOutSlowIn)), 65 | child: child, 66 | ), 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /lib/ui/widgets/custom/screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:stacked/stacked.dart'; 3 | 4 | import 'ui_helpers.dart'; 5 | 6 | /// A widget that provides frequently required functionality for any screen 7 | class ScreenBuilder extends StatelessWidget { 8 | /// Indicates if you want Provider to dispose the ViewModel when it's removed from the widget tree. 9 | /// 10 | /// default's to true 11 | final bool disposeViewModel; 12 | 13 | /// Indicate if you want the [builder] function to fire when notifyListeners is called in the ViewModel. 14 | /// 15 | /// default's to true 16 | final bool isReactive; 17 | 18 | /// A builder function that returns the ViewModel for this widget 19 | final Widget Function(BuildContext, UIHelpers, T) builder; 20 | 21 | final T viewModel; 22 | 23 | /// Fires once when the ViewModel is created or set for the first time 24 | final Function(T)? onModelReady; 25 | 26 | final Function(T)? onDispose; 27 | 28 | ScreenBuilder( 29 | {Key? key, 30 | required this.builder, 31 | required this.viewModel, 32 | this.onModelReady, 33 | this.onDispose, 34 | this.disposeViewModel = true, 35 | this.isReactive = true}) 36 | : super(key: key); 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | UIHelpers uiHelpers = UIHelpers.fromContext(context); 41 | 42 | return (isReactive) 43 | ? ViewModelBuilder.reactive( 44 | builder: (context, model, child) => 45 | SafeArea(left: true, right: true, top: false, bottom: false, child: builder(context, uiHelpers, model)), 46 | disposeViewModel: disposeViewModel, 47 | onViewModelReady: onModelReady, 48 | onDispose: onDispose, 49 | viewModelBuilder: () => viewModel) 50 | : ViewModelBuilder.nonReactive( 51 | builder: (context, model, child) => 52 | SafeArea(left: true, right: true, top: false, bottom: false, child: builder(context, uiHelpers, model)), 53 | disposeViewModel: disposeViewModel, 54 | onViewModelReady: onModelReady, 55 | onDispose: onDispose, 56 | viewModelBuilder: () => viewModel); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/ui/widgets/dialogs/confirm_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:stacked_services/stacked_services.dart'; 3 | 4 | import '../custom/base_dialog_wrapper.dart'; 5 | 6 | class ConfirmDialog extends StatelessWidget { 7 | final DialogRequest? dialogRequest; 8 | final Function(DialogResponse)? onDialogTap; 9 | final VoidCallback? onDenied; 10 | final VoidCallback? onConfirmed; 11 | final String title; 12 | final Widget content; 13 | final String? buttonTitle; 14 | final double? width; 15 | final double? height; 16 | 17 | const ConfirmDialog({ 18 | Key? key, 19 | this.dialogRequest, 20 | this.onDialogTap, 21 | this.title = '提示', 22 | this.content = const SizedBox(), 23 | this.onDenied, 24 | this.onConfirmed, 25 | this.buttonTitle = "确定", 26 | this.width, 27 | this.height, 28 | }) : super(key: key); 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return WillPopScope( 33 | onWillPop: () async { 34 | if (onDenied != null) { 35 | onDenied!(); 36 | } 37 | return false; 38 | }, 39 | child: BaseDialogWrapper( 40 | title: title, 41 | width: width, 42 | height: height, 43 | child: Center( 44 | child: content, 45 | ), 46 | onDenied: () { 47 | onDenied!(); 48 | }, 49 | onConfirmed: () { 50 | onConfirmed!(); 51 | }, 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/ui/widgets/dialogs/info_alert/info_alert_dialog_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class InfoAlertDialogModel extends BaseViewModel {} 4 | -------------------------------------------------------------------------------- /lib/ui/widgets/drawer/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertemplate/ui/views/login/login_view.dart'; 3 | 4 | import 'drawer_user_controller.dart'; 5 | import 'home_drawer.dart'; 6 | 7 | class DrawerExample extends StatefulWidget { 8 | const DrawerExample({super.key}); 9 | 10 | @override 11 | DrawerExampleState createState() => DrawerExampleState(); 12 | } 13 | 14 | class DrawerExampleState extends State { 15 | late AnimationController sliderAnimationController; 16 | @override 17 | Widget build(BuildContext context) { 18 | return Scaffold( 19 | body: DrawerUserController( 20 | screenIndex: DrawerIndex.HOME, 21 | drawerWidth: MediaQuery.of(context).size.width * 0.75, 22 | animationController: (AnimationController animationController) { 23 | sliderAnimationController = animationController; 24 | }, 25 | screenView: const LoginView(), //任何页面 26 | ), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/ui/widgets/loading/loading_animation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertemplate/core/constants/animations.dart'; 3 | import 'package:rive/rive.dart'; 4 | 5 | class LoadingAnimation extends StatelessWidget { 6 | const LoadingAnimation({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return const SizedBox( 11 | height: 50, 12 | child: RiveAnimation.asset(Animations.loader), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/ui/widgets/notice/notice_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertemplate/ui/common/app_colors.dart'; 3 | import 'package:fluttertemplate/ui/common/ui_helpers.dart'; 4 | import 'package:stacked/stacked.dart'; 5 | import 'package:stacked_services/stacked_services.dart'; 6 | 7 | import 'notice_sheet_model.dart'; 8 | 9 | class NoticeSheet extends StackedView { 10 | final Function(SheetResponse)? completer; 11 | final SheetRequest request; 12 | const NoticeSheet({ 13 | Key? key, 14 | required this.completer, 15 | required this.request, 16 | }) : super(key: key); 17 | 18 | @override 19 | Widget builder( 20 | BuildContext context, 21 | NoticeSheetModel viewModel, 22 | Widget? child, 23 | ) { 24 | return Container( 25 | padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), 26 | decoration: const BoxDecoration( 27 | color: Colors.white, 28 | borderRadius: BorderRadius.only( 29 | topLeft: Radius.circular(10), 30 | topRight: Radius.circular(10), 31 | ), 32 | ), 33 | child: Column( 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | mainAxisSize: MainAxisSize.min, 36 | children: [ 37 | Text( 38 | request.title!, 39 | style: const TextStyle(fontSize: 25, fontWeight: FontWeight.w900), 40 | ), 41 | verticalSpaceTiny, 42 | Text( 43 | request.description!, 44 | style: const TextStyle(fontSize: 14, color: kcMediumGrey), 45 | maxLines: 3, 46 | softWrap: true, 47 | ), 48 | verticalSpaceLarge, 49 | ], 50 | ), 51 | ); 52 | } 53 | 54 | @override 55 | NoticeSheetModel viewModelBuilder(BuildContext context) => NoticeSheetModel(); 56 | } 57 | -------------------------------------------------------------------------------- /lib/ui/widgets/notice/notice_sheet_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:stacked/stacked.dart'; 2 | 3 | class NoticeSheetModel extends BaseViewModel {} 4 | -------------------------------------------------------------------------------- /lib/ui/widgets/popup/popup.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /* 4 | showDialog( 5 | context: context, 6 | useRootNavigator: false, 7 | builder: (_) => Popup(child: Text("123",style: TextStyle(color: Colors.white),),), 8 | ); 9 | */ 10 | class PopRoute extends PopupRoute { 11 | final Duration _duration = Duration(milliseconds: 300); 12 | Widget child; 13 | 14 | PopRoute({required this.child}); 15 | 16 | @override 17 | Color? get barrierColor => null; 18 | 19 | @override 20 | bool get barrierDismissible => true; 21 | 22 | @override 23 | String? get barrierLabel => null; 24 | 25 | @override 26 | Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { 27 | return child; 28 | } 29 | 30 | @override 31 | Duration get transitionDuration => _duration; 32 | } 33 | 34 | class Popup extends StatelessWidget { 35 | final Widget child; 36 | final Function? onClick; //点击child事件 37 | 38 | Popup({ 39 | required this.child, 40 | this.onClick, 41 | }); 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Material( 46 | color: Colors.transparent, 47 | child: GestureDetector( 48 | child: Stack( 49 | children: [ 50 | Container( 51 | width: MediaQuery.of(context).size.width, 52 | height: MediaQuery.of(context).size.height, 53 | color: Colors.transparent, 54 | ), 55 | Positioned( 56 | bottom: 0, 57 | child: GestureDetector( 58 | child: child, 59 | onTap: () { 60 | //点击子child 61 | if (onClick != null) { 62 | Navigator.of(context).pop(); 63 | onClick!(); 64 | } 65 | }), 66 | ), 67 | ], 68 | ), 69 | onTap: () { 70 | //点击空白处 71 | Navigator.of(context).pop(); 72 | }, 73 | ), 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/ui/widgets/routeanimation/page_route_anim.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class NoAnimRouteBuilder extends PageRouteBuilder { 4 | final Widget page; 5 | 6 | NoAnimRouteBuilder(this.page) 7 | : super( 8 | opaque: false, 9 | pageBuilder: (context, animation, secondaryAnimation) => page, 10 | transitionDuration: Duration(milliseconds: 0), 11 | transitionsBuilder: 12 | (context, animation, secondaryAnimation, child) => child); 13 | } 14 | 15 | class FadeRouteBuilder extends PageRouteBuilder { 16 | final Widget page; 17 | 18 | FadeRouteBuilder(this.page) 19 | : super( 20 | pageBuilder: (context, animation, secondaryAnimation) => page, 21 | transitionDuration: Duration(milliseconds: 500), 22 | transitionsBuilder: (context, animation, secondaryAnimation, 23 | child) => 24 | FadeTransition( 25 | opacity: Tween(begin: 0.1, end: 1.0).animate(CurvedAnimation( 26 | parent: animation, 27 | curve: Curves.fastOutSlowIn, 28 | )), 29 | child: child, 30 | )); 31 | } 32 | 33 | class SlideTopRouteBuilder extends PageRouteBuilder { 34 | final Widget page; 35 | 36 | SlideTopRouteBuilder(this.page) 37 | : super( 38 | pageBuilder: (context, animation, secondaryAnimation) => page, 39 | transitionDuration: Duration(milliseconds: 800), 40 | transitionsBuilder: 41 | (context, animation, secondaryAnimation, child) => 42 | SlideTransition( 43 | position: Tween( 44 | begin: Offset(0.0, -1.0), end: Offset(0.0, 0.0)) 45 | .animate(CurvedAnimation( 46 | parent: animation, curve: Curves.fastOutSlowIn)), 47 | child: child, 48 | )); 49 | } 50 | 51 | class SizeRoute extends PageRouteBuilder { 52 | final Widget page; 53 | 54 | SizeRoute(this.page) 55 | : super( 56 | pageBuilder: (context, animation, secondaryAnimation) => page, 57 | transitionDuration: Duration(milliseconds: 300), 58 | transitionsBuilder: (context, animation, secondaryAnimation, child) => 59 | // Align( 60 | // child: SizeTransition(child: child, sizeFactor: animation), 61 | // ), 62 | ScaleTransition( 63 | scale: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( 64 | parent: animation, curve: Curves.fastOutSlowIn)), 65 | child: child, 66 | ), 67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /lib/ui/widgets/skeleton/skeleton.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shimmer/shimmer.dart'; 3 | 4 | class SkeletonBox extends StatelessWidget { 5 | final double width; 6 | final double height; 7 | final bool isCircle; 8 | 9 | SkeletonBox({required this.width, required this.height, this.isCircle = false}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | bool isDark = Theme.of(context).brightness == Brightness.dark; 14 | Divider.createBorderSide(context, width: 0.7); 15 | return Container( 16 | width: width, 17 | height: height, 18 | decoration: SkeletonDecoration(isCircle: isCircle, isDark: isDark), 19 | ); 20 | } 21 | } 22 | 23 | /// 骨架屏 元素背景 ->形状及颜色 24 | class SkeletonDecoration extends BoxDecoration { 25 | SkeletonDecoration({ 26 | isCircle = false, 27 | isDark = false, 28 | }) : super( 29 | color: !isDark ? Colors.grey[350] : Colors.grey[700], 30 | shape: isCircle ? BoxShape.circle : BoxShape.rectangle, 31 | ); 32 | } 33 | 34 | /// 骨架屏 元素背景 ->形状及颜色 35 | class BottomBorderDecoration extends BoxDecoration { 36 | BottomBorderDecoration() : super(border: Border(bottom: BorderSide(width: 0.3))); 37 | } 38 | 39 | /// 骨架屏 40 | class SkeletonList extends StatelessWidget { 41 | final EdgeInsetsGeometry padding; 42 | final int length; 43 | final IndexedWidgetBuilder builder; 44 | // 一般屏幕长度够用 45 | SkeletonList({this.length = 6, this.padding = const EdgeInsets.all(7), required this.builder}); 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | var theme = Theme.of(context); 50 | bool isDark = theme.brightness == Brightness.dark; 51 | 52 | var _baseColor = isDark ? Color(0xFF616161) : Color(0xFFD6D6D6); 53 | var highlightColor = 54 | isDark ? Color(0xFF9E9E9E) : Color.alphaBlend(theme.colorScheme.secondary.withAlpha(20), Color(0xFFF5F5F5)); 55 | 56 | return SingleChildScrollView( 57 | physics: NeverScrollableScrollPhysics(), 58 | child: Shimmer.fromColors( 59 | period: Duration(milliseconds: 1200), 60 | baseColor: _baseColor, 61 | highlightColor: highlightColor, 62 | child: Padding( 63 | padding: padding, 64 | child: Column( 65 | children: List.generate(length, (index) => builder(context, index)), 66 | ))), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/ui/widgets/slider/demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertemplate/core/utils/common/color_utils.dart'; 3 | 4 | import 'base_slider.dart'; 5 | 6 | class SliderDemo extends StatefulWidget { 7 | const SliderDemo({super.key}); 8 | 9 | @override 10 | SliderDemoState createState() => SliderDemoState(); 11 | } 12 | 13 | class SliderDemoState extends State { 14 | double progressValue = 0.0; 15 | @override 16 | Widget build(BuildContext context) { 17 | return _buildSlider(); 18 | } 19 | 20 | // SliderTheme 字段解释: https://blog.csdn.net/qq_33635385/article/details/100067702 21 | // slider 使用示例: https://www.jianshu.com/p/e51f660861a2 22 | Widget _buildSlider() { 23 | return SliderTheme( 24 | //自定义风格 25 | data: SliderTheme.of(context).copyWith( 26 | activeTrackColor: HexToColor('#BFF184'), //进度条滑块左边颜色 27 | inactiveTrackColor: HexToColor('#BFF184'), //进度条滑块右边颜色 28 | trackShape: const RoundSliderTrackShape(radius: 10), //进度条形状,这边自定义两头显示圆角 29 | thumbColor: Colors.yellow, //滑块颜色 30 | overlayColor: Colors.green, //滑块拖拽时外圈的颜色 31 | overlayShape: const RoundSliderOverlayShape( 32 | //可继承SliderComponentShape自定义形状 33 | overlayRadius: 15, //滑块外圈大小 34 | ), 35 | thumbShape: const RoundSliderThumbShape( 36 | //可继承SliderComponentShape自定义形状 37 | disabledThumbRadius: 15, //禁用是滑块大小 38 | enabledThumbRadius: 10, //滑块大小 39 | ), 40 | inactiveTickMarkColor: Colors.black, 41 | tickMarkShape: const RoundSliderTickMarkShape( 42 | //继承SliderTickMarkShape可自定义刻度形状 43 | tickMarkRadius: 4.0, //刻度大小 44 | ), 45 | showValueIndicator: ShowValueIndicator.onlyForDiscrete, //气泡显示的形式 46 | valueIndicatorColor: Colors.red, //气泡颜色 47 | valueIndicatorShape: const PaddleSliderValueIndicatorShape(), //气泡形状 48 | valueIndicatorTextStyle: const TextStyle(color: Colors.black), //气泡里值的风格 49 | trackHeight: 15, //进度条宽度 50 | ), 51 | child: Slider( 52 | value: progressValue, 53 | onChanged: (v) { 54 | setState(() => progressValue = v); 55 | }, 56 | label: "气泡:$progressValue", //气泡的值 57 | divisions: 10, //进度条上显示多少个刻度点 58 | max: 100, 59 | min: 0, 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/ui/widgets/switchanimation/animated_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ScaleAnimatedSwitcher extends StatelessWidget { 4 | final Widget child; 5 | 6 | ScaleAnimatedSwitcher({required this.child}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return AnimatedSwitcher( 11 | duration: Duration(milliseconds: 300), 12 | transitionBuilder: (child, animation) => ScaleTransition( 13 | scale: animation, 14 | child: child, 15 | ), 16 | child: child, 17 | ); 18 | } 19 | } 20 | 21 | class EmptyAnimatedSwitcher extends StatelessWidget { 22 | final bool display; 23 | final Widget child; 24 | 25 | EmptyAnimatedSwitcher({this.display = true, required this.child}); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return ScaleAnimatedSwitcher(child: display ? child : SizedBox.shrink()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | 12 | void fl_register_plugins(FlPluginRegistry* registry) { 13 | g_autoptr(FlPluginRegistrar) rive_common_registrar = 14 | fl_plugin_registry_get_registrar_for_plugin(registry, "RivePlugin"); 15 | rive_plugin_register_with_registrar(rive_common_registrar); 16 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 17 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 18 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 19 | } 20 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | rive_common 7 | url_launcher_linux 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import connectivity_plus 9 | import device_info_plus 10 | import flutter_local_notifications 11 | import package_info_plus 12 | import path_provider_foundation 13 | import rive_common 14 | import share_plus 15 | import shared_preferences_foundation 16 | import url_launcher_macos 17 | 18 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 19 | ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) 20 | DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) 21 | FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) 22 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 23 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 24 | RivePlugin.register(with: registry.registrar(forPlugin: "RivePlugin")) 25 | SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 26 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 27 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 28 | } 29 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = fluttertemplate 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.fluttertemplate 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "flutter run", 7 | "build": "flutter build apk && node copy-app.js" 8 | }, 9 | "dependencies": {}, 10 | "devDependencies": {}, 11 | "browserslist": [ 12 | "> 1%", 13 | "last 2 versions" 14 | ] 15 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: fluttertemplate 2 | description: A new Flutter project. 3 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 4 | version: 1.1.0 5 | 6 | environment: 7 | sdk: ">=3.1.3 <4.0.0" 8 | flutter: ">=3.15.0" 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | flutter_localizations: 13 | sdk: flutter 14 | 15 | # core MMVM 16 | stacked: 3.4.2 17 | stacked_services: 1.5.0 18 | 19 | # services 20 | flutter_hooks: 0.20.5 21 | dio: 5.4.3 22 | url_launcher: 6.3.0 23 | shared_preferences: 2.2.3 24 | share_plus: 9.0.0 25 | connectivity_plus: 6.0.3 26 | device_info_plus: 10.1.0 27 | flutter_dotenv: 5.1.0 28 | app_settings: 5.1.1 29 | package_info_plus: 8.0.0 30 | flutter_keyboard_visibility: 6.0.0 31 | freezed_annotation: 2.4.1 32 | 33 | # three modules 34 | rxdart: ^0.27.7 35 | built_value: ^8.9.0 36 | built_collection: ^5.1.1 37 | hive: 2.2.3 38 | hive_flutter: 1.1.0 39 | get_it: 7.7.0 40 | get: 4.6.6 41 | path_provider: 2.1.3 42 | provider: 6.1.2 43 | permission_handler: 11.3.0 44 | android_id: 0.4.0 45 | fluttertoast: 8.2.6 46 | 47 | feedback: 3.1.0 48 | flutter_spinkit: 5.2.1 49 | http_parser: 4.0.2 50 | mime: 1.0.5 51 | shimmer: 3.0.0 52 | # utils 53 | quiver: 3.2.1 54 | # animation 55 | # flare_flutter: 3.0.2 56 | rive: 0.13.7 57 | # animation 58 | lottie: 3.1.2 59 | flutter_local_notifications: 17.1.2 60 | 61 | dev_dependencies: 62 | flutter_test: 63 | sdk: flutter 64 | 65 | build_runner: ^2.4.10 66 | built_value_generator: ^8.9.2 67 | stacked_generator: ^1.6.0 68 | hive_generator: ^2.0.1 69 | flutter_lints: ^3.0.0 70 | # mock data 71 | mockito: 5.4.4 72 | logger: 2.3.0 73 | 74 | flutter: 75 | uses-material-design: true 76 | assets: 77 | - .env 78 | - assets/lang/zh.json 79 | - assets/lang/en.json 80 | 81 | - assets/sounds/Piano.sf2 82 | 83 | - assets/audio/viper.mp3 84 | - assets/audio/tapbutton.mp3 85 | 86 | - assets/webview/dist/index.html 87 | - assets/webview/dist/ 88 | - assets/webview/dist/css/ 89 | - assets/webview/dist/js/ 90 | - assets/webview/dist/musicXML/ 91 | 92 | - assets/animations/ 93 | - assets/animations/lottie/ 94 | - assets/images/ 95 | 96 | # To add custom fonts to your application, add a fonts section here, 97 | # in this "flutter" section. Each entry in this list should have a 98 | # "family" key with the font family name, and a "fonts" key with a 99 | # list giving the asset and other descriptors for the font. For 100 | # example: 101 | # fonts: 102 | # - family: Schyler 103 | # fonts: 104 | # - asset: fonts/Schyler-Regular.ttf 105 | # - asset: fonts/Schyler-Italic.ttf 106 | # style: italic 107 | # - family: Trajan Pro 108 | # fonts: 109 | # - asset: fonts/TrajanPro.ttf 110 | # - asset: fonts/TrajanPro_Bold.ttf 111 | # weight: 700 112 | # 113 | -------------------------------------------------------------------------------- /stacked.json: -------------------------------------------------------------------------------- 1 | { 2 | "bottom_sheets_path": "ui/widgets/bottom_sheets", 3 | "dialogs_path": "ui/widgets/dialogs", 4 | "line_length": 80, 5 | "locator_name": "locator", 6 | "prefer_web": false, 7 | "register_mocks_function": "registerServices", 8 | "services_path": "services", 9 | "stacked_app_file_path": "core/app/app.dart", 10 | "test_helpers_file_path": "helpers/test_helpers.dart", 11 | "test_services_path": "services", 12 | "test_views_path": "viewmodels", 13 | "test_widgets_path": "widget_models", 14 | "v1": false, 15 | "views_path": "ui/views", 16 | "widgets_path": "ui/widgets" 17 | } 18 | -------------------------------------------------------------------------------- /test/helpers/test_helpers.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluttertemplate/core/app/app.locator.dart'; 2 | import 'package:mockito/annotations.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | import 'package:stacked_services/stacked_services.dart'; 5 | // @stacked-import 6 | 7 | import 'test_helpers.mocks.dart'; 8 | 9 | @GenerateMocks([], customMocks: [ 10 | MockSpec(onMissingStub: OnMissingStub.returnDefault), 11 | MockSpec(onMissingStub: OnMissingStub.returnDefault), 12 | MockSpec(onMissingStub: OnMissingStub.returnDefault), 13 | // @stacked-mock-spec 14 | ]) 15 | void registerServices() { 16 | getAndRegisterNavigationService(); 17 | getAndRegisterBottomSheetService(); 18 | getAndRegisterDialogService(); 19 | // @stacked-mock-register 20 | } 21 | 22 | MockNavigationService getAndRegisterNavigationService() { 23 | _removeRegistrationIfExists(); 24 | final service = MockNavigationService(); 25 | locator.registerSingleton(service); 26 | return service; 27 | } 28 | 29 | MockBottomSheetService getAndRegisterBottomSheetService({ 30 | SheetResponse? showCustomSheetResponse, 31 | }) { 32 | _removeRegistrationIfExists(); 33 | final service = MockBottomSheetService(); 34 | 35 | when(service.showCustomSheet( 36 | enableDrag: anyNamed('enableDrag'), 37 | enterBottomSheetDuration: anyNamed('enterBottomSheetDuration'), 38 | exitBottomSheetDuration: anyNamed('exitBottomSheetDuration'), 39 | ignoreSafeArea: anyNamed('ignoreSafeArea'), 40 | isScrollControlled: anyNamed('isScrollControlled'), 41 | barrierDismissible: anyNamed('barrierDismissible'), 42 | additionalButtonTitle: anyNamed('additionalButtonTitle'), 43 | variant: anyNamed('variant'), 44 | title: anyNamed('title'), 45 | hasImage: anyNamed('hasImage'), 46 | imageUrl: anyNamed('imageUrl'), 47 | showIconInMainButton: anyNamed('showIconInMainButton'), 48 | mainButtonTitle: anyNamed('mainButtonTitle'), 49 | showIconInSecondaryButton: anyNamed('showIconInSecondaryButton'), 50 | secondaryButtonTitle: anyNamed('secondaryButtonTitle'), 51 | showIconInAdditionalButton: anyNamed('showIconInAdditionalButton'), 52 | takesInput: anyNamed('takesInput'), 53 | barrierColor: anyNamed('barrierColor'), 54 | barrierLabel: anyNamed('barrierLabel'), 55 | customData: anyNamed('customData'), 56 | data: anyNamed('data'), 57 | description: anyNamed('description'), 58 | )).thenAnswer((realInvocation) => Future.value(showCustomSheetResponse ?? SheetResponse())); 59 | 60 | locator.registerSingleton(service); 61 | return service; 62 | } 63 | 64 | MockDialogService getAndRegisterDialogService() { 65 | _removeRegistrationIfExists(); 66 | final service = MockDialogService(); 67 | locator.registerSingleton(service); 68 | return service; 69 | } 70 | 71 | // @stacked-mock-create 72 | 73 | void _removeRegistrationIfExists() { 74 | if (locator.isRegistered()) { 75 | locator.unregister(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/viewmodels/home_viewmodel_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:fluttertemplate/core/app/app.bottomsheets.dart'; 3 | import 'package:fluttertemplate/core/app/app.locator.dart'; 4 | import 'package:fluttertemplate/ui/common/app_strings.dart'; 5 | import 'package:fluttertemplate/ui/views/home/home_view/home_view_model.dart'; 6 | import 'package:mockito/mockito.dart'; 7 | 8 | import '../helpers/test_helpers.dart'; 9 | 10 | void main() { 11 | HomeViewModel getModel() => HomeViewModel(); 12 | 13 | group('HomeViewmodelTest -', () { 14 | setUp(() => registerServices()); 15 | tearDown(() => locator.reset()); 16 | 17 | group('incrementCounter -', () { 18 | test('When called once should return Counter is: 1', () { 19 | final model = getModel(); 20 | model.incrementCounter(); 21 | expect(model.counterLabel, 'Counter is: 1'); 22 | }); 23 | }); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /test/viewmodels/info_alert_dialog_model_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:fluttertemplate/core/app/app.locator.dart'; 3 | 4 | import '../helpers/test_helpers.dart'; 5 | 6 | void main() { 7 | group('InfoAlertDialogModel Tests -', () { 8 | setUp(() => registerServices()); 9 | tearDown(() => locator.reset()); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /test/viewmodels/notice_sheet_model_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:fluttertemplate/core/app/app.locator.dart'; 3 | 4 | import '../helpers/test_helpers.dart'; 5 | 6 | void main() { 7 | group('InfoAlertDialogModel Tests -', () { 8 | setUp(() => registerServices()); 9 | tearDown(() => locator.reset()); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | //这是一个基本的flutter小部件测试。 2 | 3 | //要在测试中执行与小部件的交互,请使用flutter提供的 WidgetTester utility 4 | 5 | // 例如,您可以发送点击和滚动手势。您还可以使用widgetter在小部件中查找子小部件树、读取文本并验证小部件属性的值是否正确。 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | import 'package:fluttertemplate/ui/views/root_component.dart'; 10 | 11 | void main() { 12 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 13 | // Build our app and trigger a frame. 14 | await tester.pumpWidget(RootComponent()); 15 | 16 | // Verify that our counter starts at 0. 17 | expect(find.text('0'), findsOneWidget); 18 | expect(find.text('1'), findsNothing); 19 | 20 | // Tap the '+' icon and trigger a frame. 21 | await tester.tap(find.byIcon(Icons.add)); 22 | await tester.pump(); 23 | 24 | // Verify that our counter has incremented. 25 | expect(find.text('0'), findsNothing); 26 | expect(find.text('1'), findsOneWidget); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | fluttertemplate 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluttertemplate", 3 | "short_name": "fluttertemplate", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | void RegisterPlugins(flutter::PluginRegistry* registry) { 16 | ConnectivityPlusWindowsPluginRegisterWithRegistrar( 17 | registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); 18 | PermissionHandlerWindowsPluginRegisterWithRegistrar( 19 | registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); 20 | RivePluginRegisterWithRegistrar( 21 | registry->GetRegistrarForPlugin("RivePlugin")); 22 | SharePlusWindowsPluginCApiRegisterWithRegistrar( 23 | registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); 24 | UrlLauncherWindowsRegisterWithRegistrar( 25 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 26 | } 27 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | connectivity_plus 7 | permission_handler_windows 8 | rive_common 9 | share_plus 10 | url_launcher_windows 11 | ) 12 | 13 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 14 | ) 15 | 16 | set(PLUGIN_BUNDLED_LIBRARIES) 17 | 18 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 19 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 20 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 23 | endforeach(plugin) 24 | 25 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 26 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 28 | endforeach(ffi_plugin) 29 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"fluttertemplate", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifredom/flutter-template-perfect/c7a13b05b3dcacf18454eca729bfd626806d81a2/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | unsigned int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length == 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | --------------------------------------------------------------------------------