├── .DS_Store
├── .gitignore
├── .idea
├── flutter_develop_template.iml
├── git_toolbox_blame.xml
├── gradle.xml
├── libraries
│ ├── Dart_Packages.xml
│ ├── Dart_SDK.xml
│ └── Flutter_Plugins.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
├── vcs.xml
└── workspace.xml
├── .vscode
└── launch.json
├── 01.png
├── 02.png
├── 03.gif
├── 04.gif
├── 05.gif
├── 06.gif
├── 07.gif
├── 08.gif
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── .idea
│ ├── caches
│ │ └── deviceStreaming.xml
│ ├── compiler.xml
│ ├── deploymentTargetSelector.xml
│ ├── git_toolbox_blame.xml
│ ├── gradle.xml
│ ├── jarRepositories.xml
│ ├── kotlinc.xml
│ ├── migrations.xml
│ ├── misc.xml
│ ├── runConfigurations.xml
│ ├── vcs.xml
│ └── workspace.xml
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── flutter_develop_template
│ │ │ │ └── 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
├── flutter_develop_template_android.iml
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── flutter_develop_template.iml
├── ios
├── .DS_Store
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
├── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
└── RunnerTests
│ └── RunnerTests.swift
├── lib
├── .DS_Store
├── common
│ ├── .DS_Store
│ ├── mvvm
│ │ ├── base_change_notifier.dart
│ │ ├── base_model.dart
│ │ ├── base_page.dart
│ │ ├── base_view_model.dart
│ │ └── base_view_model_widget.dart
│ ├── net
│ │ ├── dio_client.dart
│ │ ├── dio_interceptor.dart
│ │ └── dio_response.dart
│ ├── paging
│ │ ├── base_paging_model.dart
│ │ └── paging_data_model.dart
│ ├── repository
│ │ └── base_repository.dart
│ ├── util
│ │ └── global.dart
│ └── widget
│ │ ├── global_notification_widget.dart
│ │ ├── notifier_widget.dart
│ │ └── refresh_load_widget.dart
├── main.dart
├── main
│ ├── app.dart
│ ├── application.dart
│ ├── main_dev.dart
│ └── main_pre.dart
├── module
│ ├── .DS_Store
│ ├── home
│ │ ├── .DS_Store
│ │ ├── api
│ │ │ └── home_repository.dart
│ │ ├── model
│ │ │ └── home_list_m.dart
│ │ ├── view
│ │ │ └── home_v.dart
│ │ └── view_model
│ │ │ └── home_vm.dart
│ ├── message
│ │ ├── api
│ │ │ └── message_repository.dart
│ │ ├── model
│ │ │ └── message_list_m.dart
│ │ ├── view
│ │ │ └── message_v.dart
│ │ └── view_model
│ │ │ └── message_vm.dart
│ ├── order
│ │ ├── view
│ │ │ └── order_v.dart
│ │ └── view_model
│ │ │ └── order_vm.dart
│ ├── personal
│ │ ├── api
│ │ │ └── personal_repository.dart
│ │ ├── model
│ │ │ └── user_info_m.dart
│ │ ├── view
│ │ │ └── personal_v.dart
│ │ └── view_model
│ │ │ └── personal_vm.dart
│ └── test_fluro
│ │ ├── page_a.dart
│ │ ├── page_a2.dart
│ │ ├── page_b.dart
│ │ ├── page_c.dart
│ │ └── page_d.dart
├── res
│ ├── .DS_Store
│ ├── string
│ │ ├── str_common.dart
│ │ ├── str_home.dart
│ │ ├── str_message.dart
│ │ ├── str_order.dart
│ │ └── str_personal.dart
│ └── style
│ │ ├── color_styles.dart
│ │ ├── text_styles.dart
│ │ └── theme_styles.dart
└── router
│ ├── navigator_util.dart
│ ├── page_route_observer.dart
│ └── routers.dart
├── pubspec.yaml
└── test
└── widget_test.dart
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://www.dartlang.org/guides/libraries/private-files
2 |
3 | # Files and directories created by pub
4 | .dart_tool/
5 | .packages
6 | build/
7 | # If you're building an application, you may want to check-in your pubspec.lock
8 | pubspec.lock
9 |
10 | # Directory created by dartdoc
11 | # If you don't generate documentation locally you can remove this line.
12 | doc/api/
13 |
14 | # dotenv environment variables file
15 | .env*
16 |
17 | # Avoid committing generated Javascript files:
18 | *.dart.js
19 | *.info.json # Produced by the --dump-info flag.
20 | *.js # When generated by dart2js. Don't specify *.js if your
21 | # project includes source files written in JavaScript.
22 | *.js_
23 | *.js.deps
24 | *.js.map
25 |
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 |
--------------------------------------------------------------------------------
/.idea/flutter_develop_template.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/git_toolbox_blame.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/libraries/Dart_SDK.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/.idea/libraries/Flutter_Plugins.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
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 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | {
54 | "associatedIndex": 5
55 | }
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | {
64 | "keyToString": {
65 | "Flutter.main.dart.executor": "Run",
66 | "Flutter.main_dev.dart.executor": "Run",
67 | "Flutter.main_develop.dart.executor": "Run",
68 | "RunOnceActivity.ShowReadmeOnStart": "true",
69 | "RunOnceActivity.cidr.known.project.marker": "true",
70 | "RunOnceActivity.readMode.enableVisualFormatting": "true",
71 | "SHARE_PROJECT_CONFIGURATION_FILES": "true",
72 | "cf.first.check.clang-format": "false",
73 | "cidr.known.project.marker": "true",
74 | "dart.analysis.tool.window.visible": "false",
75 | "git-widget-placeholder": "master",
76 | "io.flutter.reload.alreadyRun": "true",
77 | "kotlin-language-version-configured": "true",
78 | "last_opened_file_path": "/Users/hanxuguang/Documents/develop/technology_task/flutter/flutter_project/flutter_develop_template",
79 | "settings.editor.selected.configurable": "flutter.settings",
80 | "show.migrate.to.gradle.popup": "false"
81 | }
82 | }
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | 1717291018554
123 |
124 |
125 | 1717291018554
126 |
127 |
128 |
129 |
130 |
131 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "launch main",
6 | "request": "launch",
7 | "type": "dart",
8 | "program": "lib/main.dart"
9 | },
10 | {
11 | "name": "attach main",
12 | "request": "attach",
13 | "type": "dart",
14 | "program": "lib/main.dart"
15 | },
16 | {
17 | "name": "launch dev",
18 | "request": "launch",
19 | "type": "dart",
20 | "program": "lib/main/main_dev.dart"
21 | },
22 | {
23 | "name": "attach dev",
24 | "request": "attach",
25 | "type": "dart",
26 | "program": "lib/main/main_dev.dart"
27 | },
28 | {
29 | "name": "launch pre",
30 | "request": "launch",
31 | "type": "dart",
32 | "program": "lib/main/main_pre.dart"
33 | },
34 | {
35 | "name": "attach pre",
36 | "request": "attach",
37 | "type": "dart",
38 | "program": "lib/main/main_pre.dart"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/01.png
--------------------------------------------------------------------------------
/02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/02.png
--------------------------------------------------------------------------------
/03.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/03.gif
--------------------------------------------------------------------------------
/04.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/04.gif
--------------------------------------------------------------------------------
/05.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/05.gif
--------------------------------------------------------------------------------
/06.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/06.gif
--------------------------------------------------------------------------------
/07.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/07.gif
--------------------------------------------------------------------------------
/08.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/08.gif
--------------------------------------------------------------------------------
/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 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at https://dart.dev/lints.
17 | #
18 | # Instead of disabling a lint rule for the entire project in the
19 | # section below, it can also be suppressed for a single line of code
20 | # or a specific dart file by using the `// ignore: name_of_lint` and
21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
22 | # producing the lint.
23 | rules:
24 | use_key_in_widget_constructors: false
25 | prefer_const_constructors: false
26 | package_names: null
27 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
28 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
29 |
30 | # Additional information about this file can be found at
31 | # https://dart.dev/guides/language/analysis-options
32 |
--------------------------------------------------------------------------------
/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/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/.idea/deploymentTargetSelector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/.idea/git_toolbox_blame.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/.idea/gradle.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/android/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
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 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/android/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/android/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
5 | id "dev.flutter.flutter-gradle-plugin"
6 | }
7 |
8 | def localProperties = new Properties()
9 | def localPropertiesFile = rootProject.file("local.properties")
10 | if (localPropertiesFile.exists()) {
11 | localPropertiesFile.withReader("UTF-8") { reader ->
12 | localProperties.load(reader)
13 | }
14 | }
15 |
16 | def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
17 | if (flutterVersionCode == null) {
18 | flutterVersionCode = "1"
19 | }
20 |
21 | def flutterVersionName = localProperties.getProperty("flutter.versionName")
22 | if (flutterVersionName == null) {
23 | flutterVersionName = "1.0"
24 | }
25 |
26 | android {
27 | namespace = "com.example.flutter_develop_template"
28 | compileSdk = flutter.compileSdkVersion
29 | ndkVersion = flutter.ndkVersion
30 |
31 | compileOptions {
32 | sourceCompatibility = JavaVersion.VERSION_1_8
33 | targetCompatibility = JavaVersion.VERSION_1_8
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.flutter_develop_template"
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 |
3 |
4 |
10 |
19 |
23 |
27 |
28 |
29 |
30 |
31 |
32 |
34 |
37 |
38 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/flutter_develop_template/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.flutter_develop_template
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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/nexus/content/groups/public/' }
6 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' }
7 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
8 | maven { url 'https://maven.aliyun.com/nexus/content/repositories/gradle-plugin' }
9 | }
10 | }
11 |
12 | rootProject.buildDir = "../build"
13 | subprojects {
14 | project.buildDir = "${rootProject.buildDir}/${project.name}"
15 | }
16 | subprojects {
17 | project.evaluationDependsOn(":app")
18 | }
19 |
20 | tasks.register("clean", Delete) {
21 | delete rootProject.buildDir
22 | }
23 |
--------------------------------------------------------------------------------
/android/flutter_develop_template_android.iml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.java.home=/Users/hanxuguang/Documents/develop/technology_task/java_server/java_profile/jdk/jdk-17.0.12.jdk/Contents/Home
2 | org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/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://mirrors.cloud.tencent.com/gradle/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 |
--------------------------------------------------------------------------------
/flutter_develop_template.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ios/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/ios/.DS_Store
--------------------------------------------------------------------------------
/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? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '12.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - package_info_plus (0.4.5):
4 | - Flutter
5 | - Sentry/HybridSDK (8.25.0)
6 | - sentry_flutter (7.20.2):
7 | - Flutter
8 | - FlutterMacOS
9 | - Sentry/HybridSDK (= 8.25.0)
10 |
11 | DEPENDENCIES:
12 | - Flutter (from `Flutter`)
13 | - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
14 | - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
15 |
16 | SPEC REPOS:
17 | trunk:
18 | - Sentry
19 |
20 | EXTERNAL SOURCES:
21 | Flutter:
22 | :path: Flutter
23 | package_info_plus:
24 | :path: ".symlinks/plugins/package_info_plus/ios"
25 | sentry_flutter:
26 | :path: ".symlinks/plugins/sentry_flutter/ios"
27 |
28 | SPEC CHECKSUMS:
29 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
30 | package_info_plus: 580e9a5f1b6ca5594e7c9ed5f92d1dfb2a66b5e1
31 | Sentry: cd86fc55628f5b7c572cabe66cc8f95a9d2f165a
32 | sentry_flutter: 99aff87df84da27d0c73bc58b8f9399ef73811bc
33 |
34 | PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
35 |
36 | COCOAPODS: 1.11.0
37 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import 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/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/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/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Flutter Develop Template
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | flutter_develop_template
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/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/lib/.DS_Store
--------------------------------------------------------------------------------
/lib/common/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/lib/common/.DS_Store
--------------------------------------------------------------------------------
/lib/common/mvvm/base_change_notifier.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class BaseChangeNotifier extends ChangeNotifier {
4 |
5 | /// 刷新UI
6 | refreshState() => notifyListeners();
7 |
8 | }
--------------------------------------------------------------------------------
/lib/common/mvvm/base_model.dart:
--------------------------------------------------------------------------------
1 | import 'base_view_model.dart';
2 |
3 | class BaseModel {
4 |
5 | VM? vm;
6 |
7 | void onDispose() {
8 | vm = null;
9 | }
10 |
11 | }
--------------------------------------------------------------------------------
/lib/common/mvvm/base_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'dart:ui';
3 |
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/scheduler.dart';
6 | import 'package:flutter_develop_template/common/mvvm/base_model.dart';
7 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
8 | import 'package:flutter_develop_template/common/mvvm/base_view_model_widget.dart';
9 | import 'package:flutter_develop_template/common/paging/base_paging_model.dart';
10 | import 'package:flutter_develop_template/main/application.dart';
11 |
12 | import '../../res/string/str_common.dart';
13 | import '../widget/global_notification_widget.dart';
14 |
15 | abstract class BaseStatefulPage extends BaseViewModelStatefulWidget {
16 | BaseStatefulPage({super.key});
17 |
18 | @override
19 | BaseStatefulPageState createState();
20 | }
21 |
22 | abstract class BaseStatefulPageState
23 | extends BaseViewModelStatefulWidgetState
24 | with AutomaticKeepAliveClientMixin {
25 |
26 | /// 定义对应的 viewModel
27 | VM? viewModel;
28 |
29 | /// 监听应用生命周期
30 | AppLifecycleListener? lifecycleListener;
31 |
32 | /// 获取应用状态
33 | AppLifecycleState? get lifecycleState =>
34 | SchedulerBinding.instance.lifecycleState;
35 |
36 | /// 是否打印 监听应用生命周期的 日志
37 | bool debugPrintLifecycleLog = false;
38 |
39 | /// 进行初始化ViewModel相关操作
40 | @override
41 | void initState() {
42 | super.initState();
43 |
44 | /// 初始化页面 属性、对象、绑定监听
45 | initAttribute();
46 | initObserver();
47 |
48 | /// 初始化ViewModel,并同步生命周期
49 | viewModel = viewBindingViewModel();
50 |
51 | /// 调用viewModel的生命周期,比如 初始化 请求网络数据 等
52 | viewModel?.onCreate();
53 |
54 | /// Flutter 低版本 使用 WidgetsBindingObserver,高版本 使用 AppLifecycleListener
55 | lifecycleListener = AppLifecycleListener(
56 | // 监听状态回调
57 | onStateChange: onStateChange,
58 |
59 | // 可见,并且可以响应用户操作时的回调
60 | onResume: onResume,
61 |
62 | // 可见,但无法响应用户操作时的回调
63 | onInactive: onInactive,
64 |
65 | // 隐藏时的回调
66 | onHide: onHide,
67 |
68 | // 显示时的回调
69 | onShow: onShow,
70 |
71 | // 暂停时的回调
72 | onPause: onPause,
73 |
74 | // 暂停后恢复时的回调
75 | onRestart: onRestart,
76 |
77 | // 当退出 并将所有视图与引擎分离时的回调(IOS 支持,Android 不支持)
78 | onDetach: onDetach,
79 |
80 | // 在退出程序时,发出询问的回调(IOS、Android 都不支持)
81 | onExitRequested: onExitRequested,
82 | );
83 |
84 | /// 页面布局完成后的回调函数
85 | lifecycleListener?.binding.addPostFrameCallback((_) {
86 | assert(context != null, 'addPostFrameCallback throw Error context');
87 |
88 | /// 初始化 需要context 的属性、对象、绑定监听
89 | initContextAttribute(context);
90 | initContextObserver(context);
91 | });
92 | }
93 |
94 | @override
95 | void didChangeDependencies() {
96 | assert((){
97 | debugPrint('BaseStatefulPage.didChangeDependencies --- ${GlobalOperateProvider.getGlobalOperate(context: context)}');
98 | return true;
99 | }());
100 | }
101 |
102 | /// 监听状态
103 | onStateChange(AppLifecycleState state) => mLog('app_state:$state');
104 |
105 | /// =============================== 根据应用状态产生的各种回调 ===============================
106 |
107 | /// 可见,并且可以响应用户操作时的回调
108 | /// 比如从应用后台调度到前台时,在 onShow() 后面 执行
109 | onResume() => mLog('onResume');
110 |
111 | /// 可见,但无法响应用户操作时的回调
112 | onInactive() => mLog('onInactive');
113 |
114 | /// 隐藏时的回调
115 | onHide() => mLog('onHide');
116 |
117 | /// 显示时的回调,从应用后台调度到前台时
118 | onShow() => mLog('onShow');
119 |
120 | /// 暂停时的回调
121 | onPause() => mLog('onPause');
122 |
123 | /// 暂停后恢复时的回调
124 | onRestart() => mLog('onRestart');
125 |
126 | /// 这两个回调,不是所有平台都支持,
127 |
128 | /// 当退出 并将所有视图与引擎分离时的回调(IOS 支持,Android 不支持)
129 | onDetach() => mLog('onDetach');
130 |
131 | /// 在退出程序时,发出询问的回调(IOS、Android 都不支持)
132 | /// 响应 [AppExitResponse.exit] 将继续终止,响应 [AppExitResponse.cancel] 将取消终止。
133 | Future onExitRequested() async {
134 | mLog('onExitRequested');
135 | return AppExitResponse.exit;
136 | }
137 |
138 | /// BaseStatefulPageState的子类,重写 dispose()
139 | /// 一定要执行父类 dispose(),防止内存泄漏
140 | @override
141 | void dispose() {
142 | /// 销毁顺序
143 |
144 | /// 1、Model 销毁其持有的 ViewModel
145 | if(viewModel?.pageDataModel?.data is BaseModel?) {
146 | BaseModel? baseModel = viewModel?.pageDataModel?.data as BaseModel?;
147 | baseModel?.onDispose();
148 | }
149 | if(viewModel?.pageDataModel?.data is BasePagingModel?) {
150 | BasePagingModel? basePagingModel = viewModel?.pageDataModel?.data as BasePagingModel?;
151 | basePagingModel?.onDispose();
152 | }
153 |
154 | /// 2、ViewModel 销毁其持有的 View
155 | /// 3、ViewModel 销毁其持有的 Model
156 | viewModel?.onDispose();
157 |
158 | /// 4、View 销毁其持有的 ViewModel
159 | viewModel = null;
160 |
161 | /// 5、销毁监听App生命周期方法
162 | lifecycleListener?.dispose();
163 | super.dispose();
164 | }
165 |
166 | /// 是否保持页面状态
167 | @override
168 | bool get wantKeepAlive => false;
169 |
170 | /// View 持有对应的 ViewModel
171 | VM viewBindingViewModel();
172 |
173 | /// 子类重写,初始化 属性、对象
174 | /// 这里不是 网络请求操作,而是页面的初始化数据
175 | /// 网络请求操作,建议在viewModel.onCreate() 中实现
176 | void initAttribute();
177 |
178 | /// 子类重写,初始化 需要 context 的属性、对象
179 | void initContextAttribute(BuildContext context) {}
180 |
181 | /// 子类重写,初始化绑定监听
182 | void initObserver();
183 |
184 | /// 子类重写,初始化需要 context 的绑定监听
185 | void initContextObserver(BuildContext context) {}
186 |
187 | /// 输出日志
188 | void mLog(String info) {
189 | if (debugPrintLifecycleLog) {
190 | assert(() {
191 | debugPrint('--- $info');
192 | return true;
193 | }());
194 | }
195 | }
196 |
197 | /// 手机应用
198 | Widget appBuild(BuildContext context) => SizedBox();
199 |
200 | /// Web
201 | Widget webBuild(BuildContext context) => SizedBox();
202 |
203 | /// PC应用
204 | Widget pcBuild(BuildContext context) => SizedBox();
205 |
206 | @override
207 | Widget build(BuildContext context) {
208 | /// 使用 AutomaticKeepAliveClientMixin 需要 super.build(context);
209 | ///
210 | /// 注意:AutomaticKeepAliveClientMixin 只是保存页面状态,并不影响 build 方法执行
211 | /// 比如 PageVie的 子页面 使用了AutomaticKeepAliveClientMixin 保存状态,
212 | /// PageView切换子页面时,子页面的build的还是会执行
213 | if(wantKeepAlive) {
214 | super.build(context);
215 | }
216 |
217 | /// 和 GlobalNotificationWidget,建立依赖关系
218 | if(EnvConfig.isGlobalNotification) {
219 | GlobalNotificationWidget.of(context);
220 | }
221 |
222 | switch (EnvConfig.platform) {
223 | case ApplicationPlatform.app: {
224 | if (Platform.isAndroid || Platform.isIOS) {
225 | // 如果,还想根据当前设备屏幕尺寸细分,
226 | // 使用MediaQuery,拿到当前设备信息,进一步适配
227 | return appBuild(context);
228 | }
229 | }
230 | case ApplicationPlatform.web: {
231 | return webBuild(context);
232 | }
233 | case ApplicationPlatform.pc: {
234 | if(Platform.isWindows || Platform.isMacOS) {
235 | return pcBuild(context);
236 | }
237 | }
238 | }
239 | return Center(
240 | child: Text(StrCommon.platformNotAdapted),
241 | );
242 | }
243 |
244 | }
245 |
--------------------------------------------------------------------------------
/lib/common/mvvm/base_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 |
3 | import '../widget/notifier_widget.dart';
4 | import 'base_page.dart';
5 |
6 | /// 基类
7 | abstract class BaseViewModel {
8 |
9 | }
10 |
11 | /// 页面继承的ViewModel,不直接使用 BaseViewModel,
12 | /// 是因为BaseViewModel基类里代码,还是不要太多为好,扩展创建新的子类就好
13 | abstract class PageViewModel extends BaseViewModel {
14 |
15 | /// 定义对应的 view
16 | BaseStatefulPageState? viewState;
17 |
18 | /// 使用 直接拿它
19 | T get state => viewState as T;
20 |
21 | /// 页面数据model
22 | PageDataModel? pageDataModel;
23 |
24 | /// 尽量在onCreate方法中编写初始化逻辑
25 | void onCreate();
26 |
27 | /// 对应的widget被销毁了,销毁相关引用对象,避免内存泄漏
28 | void onDispose() {
29 | viewState = null;
30 | pageDataModel = null;
31 | }
32 |
33 | /// 请求数据
34 | Future requestData({Map? params});
35 |
36 | }
--------------------------------------------------------------------------------
/lib/common/mvvm/base_view_model_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
3 |
4 | /// 定义ViewModelWidget基类
5 | abstract class BaseViewModelStatefulWidget extends StatefulWidget {
6 | BaseViewModelStatefulWidget({super.key});
7 |
8 | @override
9 | BaseViewModelStatefulWidgetState createState();
10 | }
11 |
12 | /// 定义ViewModelState
13 | abstract class BaseViewModelStatefulWidgetState extends State {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/lib/common/net/dio_client.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:dio/dio.dart';
4 | import 'package:dio/io.dart';
5 |
6 | import '../../main/application.dart';
7 | import 'dio_interceptor.dart';
8 |
9 | /// 默认超时时间
10 | const defaultTimeout = 60 * 1000;
11 |
12 | class DioClient extends DioForNative {
13 | /// 单例
14 | static DioClient? _instance;
15 |
16 | factory DioClient() => _instance ??= DioClient._init();
17 |
18 | /// 初始化方法
19 | DioClient._init() {
20 | options = BaseOptions(
21 | // 设置基础配置
22 | connectTimeout: Duration(milliseconds: defaultTimeout), // 连接超时时间
23 | receiveTimeout: Duration(milliseconds: defaultTimeout), // 接收超时时间
24 | sendTimeout: Duration(milliseconds: defaultTimeout), // 发送超时时间
25 | baseUrl: EnvConfig.baseUrl
26 | // ... ...
27 | );
28 | // 拦截器
29 | interceptors.add(DioInterceptor()); // 拦截器
30 |
31 | // 代理抓包(
32 | // 注意:
33 | // 1、我使用的是Mac电脑,抓包工具是 Charles,建议正式上线时,关闭此功能
34 | // 2、如果开启了抓包,但没有启动 抓包工具,Dio 会报 连接异常 DioException [connection error]
35 | // https://juejin.cn/post/7131928652568231966
36 | // https://juejin.cn/post/7035652365826916366
37 | proxy();
38 | }
39 |
40 | /// ResultFul API 风格
41 | // GET:从服务器获取一项或者多项数据
42 | // POST:在服务器新建一个资源
43 | // PUT:在服务器更新所有资源
44 | // PATCH:更新部分属性
45 | // DELETE:从服务器删除资源
46 |
47 | /// Get请求
48 | Future doGet(
49 | path, {
50 | String? baseUrl,
51 | Map? headers,
52 | ResponseType responseType = ResponseType.json,
53 | Object? data,
54 | Map? params,
55 | Options? cOptions,
56 | CancelToken? cancelToken,
57 | ProgressCallback? onReceiveProgress,
58 | }) {
59 | updateBaseOptions(baseUrl, headers, responseType); // 动态修改默认BaseOptions
60 | return get(
61 | path,
62 | data: data,
63 | queryParameters: params,
64 | options: cOptions,
65 | onReceiveProgress: onReceiveProgress,
66 | cancelToken: cancelToken,
67 | );
68 | }
69 |
70 | /// Post 请求
71 | Future doPost(
72 | path, {
73 | String? baseUrl,
74 | Map? headers,
75 | ResponseType responseType = ResponseType.json,
76 | Object? data,
77 | Map? params,
78 | Options? cOptions,
79 | CancelToken? cancelToken,
80 | ProgressCallback? onSendProgress,
81 | ProgressCallback? onReceiveProgress,
82 | }) {
83 | updateBaseOptions(baseUrl, headers, responseType); // 动态修改默认BaseOptions
84 | return post(path,
85 | data: data,
86 | queryParameters: params,
87 | options: cOptions,
88 | cancelToken: cancelToken,
89 | onSendProgress: onSendProgress,
90 | onReceiveProgress: onReceiveProgress);
91 | }
92 |
93 | /// Put 请求
94 | Future doPut(path,
95 | {String? baseUrl,
96 | Map? headers,
97 | ResponseType responseType = ResponseType.json,
98 | Object? data,
99 | Map? params,
100 | Options? cOptions,
101 | CancelToken? cancelToken,
102 | ProgressCallback? onSendProgress,
103 | ProgressCallback? onReceiveProgress}) {
104 | updateBaseOptions(baseUrl, headers, responseType); // 动态修改默认BaseOptions
105 | return put(path,
106 | data: data,
107 | queryParameters: params,
108 | options: cOptions,
109 | cancelToken: cancelToken,
110 | onSendProgress: onSendProgress,
111 | onReceiveProgress: onReceiveProgress);
112 | }
113 |
114 | /// Patch 请求
115 | Future doPatch(path,
116 | {String? baseUrl,
117 | Map? headers,
118 | ResponseType responseType = ResponseType.json,
119 | Object? data,
120 | Map? params,
121 | Options? cOptions,
122 | CancelToken? cancelToken,
123 | ProgressCallback? onSendProgress,
124 | ProgressCallback? onReceiveProgress}) {
125 | updateBaseOptions(baseUrl, headers, responseType); // 动态修改默认BaseOptions
126 | return path(path,
127 | listData: data,
128 | queryParameters: params,
129 | options: cOptions,
130 | cancelToken: cancelToken,
131 | onSendProgress: onSendProgress,
132 | onReceiveProgress: onReceiveProgress);
133 | }
134 |
135 | /// Delete 请求
136 | Future doDelete(path,
137 | {String? baseUrl,
138 | Map? headers,
139 | ResponseType responseType = ResponseType.json,
140 | Object? data,
141 | Map? params,
142 | Options? cOptions,
143 | CancelToken? cancelToken}) {
144 | updateBaseOptions(baseUrl, headers, responseType); // 动态修改默认BaseOptions
145 | return delete(
146 | path,
147 | data: data,
148 | queryParameters: params,
149 | options: cOptions,
150 | cancelToken: cancelToken,
151 | );
152 | }
153 |
154 | /// 上传文件
155 | Future uploadFile(
156 | path, {
157 | String? baseUrl,
158 | Map? headers,
159 | ResponseType responseType = ResponseType.json,
160 | Object? data,
161 | Map? params,
162 | Options? cOptions,
163 | CancelToken? cancelToken,
164 | ProgressCallback? onSendProgress,
165 | ProgressCallback? onReceiveProgress,
166 | }) {
167 | updateBaseOptions(baseUrl, headers, responseType); // 动态修改默认BaseOptions
168 | options.contentType = Headers.multipartFormDataContentType;
169 | return post(path,
170 | data: data,
171 | queryParameters: params,
172 | options: cOptions,
173 | cancelToken: cancelToken,
174 | onSendProgress: onSendProgress,
175 | onReceiveProgress: onReceiveProgress);
176 | }
177 |
178 | /// 动态修改 BaseOptions
179 | void updateBaseOptions(
180 | String? baseUrl,
181 | Map? headers,
182 | ResponseType responseType,
183 | ) {
184 | if (baseUrl != null) {
185 | options.baseUrl = baseUrl;
186 | }
187 | if (headers != null) {
188 | options.headers = headers;
189 | }
190 | if (responseType != ResponseType.json) {
191 | options.responseType = responseType;
192 | }
193 | }
194 |
195 | /// 代理抓包,测试阶段可能需要
196 | void proxy() {
197 | if (EnvConfig.proxyEnable) {
198 | if (EnvConfig.caughtAddress?.isNotEmpty ?? false) {
199 | (httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
200 | final client = HttpClient();
201 | client.findProxy = (uri) => 'PROXY ' + EnvConfig.caughtAddress!;
202 |
203 | client.badCertificateCallback = (cert, host, port) => true;
204 | return client;
205 | };
206 | }
207 | }
208 | }
209 |
210 | }
211 |
--------------------------------------------------------------------------------
/lib/common/net/dio_interceptor.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_develop_template/main/app.dart';
4 | import 'package:flutter_develop_template/main/application.dart';
5 |
6 | import '../widget/global_notification_widget.dart';
7 | import 'dio_response.dart';
8 |
9 | final DateTime dateTime = DateTime.now();
10 |
11 | /// Dio拦截器
12 | class DioInterceptor extends InterceptorsWrapper {
13 | /// 请求发起前,调用的方法
14 | /// 可以在这里动态修改Header里信息,从options里获取原来的Header信息,进行修改
15 | /// 常见的场景有:弹出加载loading、添加Token
16 | @override
17 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
18 | // options.headers['token'] = 'xxx'; // 设置 token
19 |
20 | // 打印请求日志信息
21 | assert(() {
22 | debugPrint(
23 | '${dateTime}:${options.method} request:${options.baseUrl + options.path}');
24 | debugPrint('params:${options.queryParameters}');
25 | return true;
26 | }());
27 |
28 | // 或者刷新token时效
29 |
30 | /// 重置 全局操作状态
31 | if (EnvConfig.isGlobalNotification) {
32 | GlobalOperateProvider.runGlobalOperate(
33 | context: navigatorKey.currentContext, operate: GlobalOperate.idle);
34 | }
35 |
36 | /// 必须要写的代码,表示进入下一步
37 | handler.next(options);
38 | }
39 |
40 | /// 请求成功后,执行的响应方法
41 | @override
42 | void onResponse(Response response, ResponseInterceptorHandler handler) {
43 | // 打印响应日志信息
44 |
45 | // 打印请求日志信息
46 | assert(() {
47 | debugPrint('${dateTime}:response:${response.statusCode}');
48 | // debugPrint('data:${response.data.toString()}');
49 | return true;
50 | }());
51 |
52 | if (response.statusCode == 200) {
53 | /// 访问成功
54 |
55 | /// 有返回值的情况,转实体
56 | if (response.data is Map) {
57 | ResponseData responseData = ResponseData.fromJson(response.data);
58 |
59 | /// 成功
60 | if (responseData.success) {
61 | response.statusCode = responseData.statusCode;
62 | response.data = responseData.data;
63 | } else {
64 | /// 走到这,说明访问成功,但业务不允许,比如没有权限
65 | response.statusCode = responseData.statusCode;
66 | response.statusMessage = responseData.statusMsgConversion;
67 | }
68 |
69 | }
70 |
71 | }
72 |
73 | /// 必须要写的代码,表示进入下一步
74 | return handler.resolve(response);
75 | }
76 |
77 | /// 这里的异常,属于Dio自身的异常
78 | @override
79 | void onError(DioException err, ErrorInterceptorHandler handler) {
80 | throw DioException(
81 | requestOptions: err.requestOptions,
82 | type: err.type,
83 | error: err,
84 | response: err.response);
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/lib/common/net/dio_response.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 |
3 | import '../../res/string/str_common.dart';
4 |
5 | /// 响应的数据模型,根据你当前后台返回的数据来自定义,我这里只是做个例子
6 | abstract class BaseResponseData {
7 | int? statusCode;
8 | String? statusMessage;
9 | dynamic data;
10 | }
11 |
12 | const int REQUEST_SUCCESS = 0;
13 | class ResponseData extends BaseResponseData {
14 |
15 | bool get success => statusCode == REQUEST_SUCCESS;
16 |
17 | String? get statusMsgConversion => serviceFailureConversionText(statusCode);
18 |
19 | /// 这里是 业务逻辑不通过
20 | /// code 由后台人员自定义
21 | String? serviceFailureConversionText(int? errCode) {
22 | String str;
23 | switch (errCode) {
24 | case -1 : {
25 | str = statusMessage ?? '-1';
26 | } break;
27 | default: {
28 | str = '未知错误';
29 | }
30 | }
31 | return str;
32 | }
33 |
34 | ResponseData.fromJson(Map json) {
35 | statusCode = json['errorCode'] ?? json['code'];
36 | statusMessage = json['errorMsg'] ?? json['message'];
37 | data = json['data'] ?? json['data'];
38 | }
39 |
40 | }
41 |
42 | /// 根据Dio异常类型 转换为 文字
43 | String? dioErrorConversionText(DioException e) {
44 | String str;
45 | switch (e.type) {
46 | case DioExceptionType.connectionTimeout:
47 | str = '[DioExceptionType.connectionTimeout] ${StrCommon.connectionTimeout}';
48 | break;
49 | case DioExceptionType.sendTimeout:
50 | str = '[DioExceptionType.sendTimeout] ${StrCommon.sendTimeout}';
51 | break;
52 | case DioExceptionType.receiveTimeout:
53 | str = '[DioExceptionType.receiveTimeout] ${StrCommon.receiveTimeout}';
54 | break;
55 | case DioExceptionType.badCertificate:
56 | str = '[DioExceptionType.badCertificate] ${StrCommon.accessCertificateError}';
57 | break;
58 | case DioExceptionType.badResponse:
59 | str = '[DioExceptionType.badResponse] ${StrCommon.validationFailed}';
60 | break;
61 | case DioExceptionType.connectionError:
62 | str = '[DioExceptionType.connectionError] ${StrCommon.connectionIsAbnormal}';
63 | break;
64 | case DioExceptionType.unknown:
65 | default:
66 | str = '[DioExceptionType.unknown] ${StrCommon.unknownError}';
67 | break;
68 | }
69 | return str;
70 | }
71 |
72 |
73 | /// 根据Dio异常code 转换为 文字
74 | String? codeConversionText(int? statusCode) {
75 | String str;
76 | if (statusCode != null) {
77 | switch (statusCode) {
78 | case 400:
79 | str = '[$statusCode] ${StrCommon.parameterIsIncorrect}';
80 | break;
81 | case 402:
82 | str = '[$statusCode] ${StrCommon.illegalRequests}';
83 | break;
84 | case 403:
85 | str = '[$statusCode] ${StrCommon.serverRejectsRequest}';
86 | break;
87 | case 404:
88 | str = '[$statusCode] ${StrCommon.accessAddressDoesNotExist}';
89 | break;
90 | case 405:
91 | str = '[$statusCode] ${StrCommon.requestIsMadeWrongWay}';
92 | break;
93 | case 500:
94 | str = '[$statusCode] ${StrCommon.wasAnErrorInsideServer}';
95 | break;
96 | case 502:
97 | str = '[$statusCode] ${StrCommon.invalidRequest}';
98 | break;
99 | case 503:
100 | str = '[$statusCode] ${StrCommon.serverIsBusy}';
101 | break;
102 | case 505:
103 | str = '[$statusCode] ${StrCommon.unsupportedHttpProtocol}';
104 | break;
105 | default:
106 | str = '[$statusCode] ${StrCommon.unknownError}';
107 | break;
108 | }
109 | } else {
110 | str = '[$statusCode] ${StrCommon.unknownError}';
111 | }
112 | return str;
113 | }
114 |
115 |
116 |
--------------------------------------------------------------------------------
/lib/common/paging/base_paging_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_develop_template/common/mvvm/base_model.dart';
2 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
3 |
4 | /// 内部 有分页列表集合 的实体需要继承 BasePagingModel
5 | class BasePagingModel extends BaseModel{
6 | int? curPage;
7 | List? datas;
8 | int? offset;
9 | bool? over;
10 | int? pageCount;
11 | int? size;
12 | int? total;
13 |
14 | BasePagingModel({this.curPage, this.datas, this.offset, this.over,
15 | this.pageCount, this.size, this.total});
16 |
17 | }
18 |
19 | /// 是分页列表 集合子项 实体需要继承 BasePagingItem
20 | class BasePagingItem {}
--------------------------------------------------------------------------------
/lib/common/paging/paging_data_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_develop_template/common/mvvm/base_change_notifier.dart';
3 |
4 | import '../mvvm/base_view_model.dart';
5 | import '../widget/notifier_widget.dart';
6 |
7 | /// 分页数据相关
8 |
9 | /// 分页行为:下拉刷新/上拉加载更多
10 | enum PagingBehavior {
11 | /// 空闲,默认状态
12 | idle,
13 |
14 | /// 加载
15 | load,
16 |
17 | /// 刷新
18 | refresh;
19 | }
20 |
21 | /// 分页状态:执行完 下拉刷新/上拉加载更多后,得到的状态
22 | enum PagingState {
23 | /// 空闲,默认状态
24 | idle,
25 |
26 | /// 加载成功
27 | loadSuccess,
28 |
29 | /// 加载失败
30 | loadFail,
31 |
32 | /// 没有更多数据了
33 | loadNoData,
34 |
35 | /// 正在加载
36 | curLoading,
37 |
38 | /// 刷新成功
39 | refreshSuccess,
40 |
41 | /// 刷新失败
42 | refreshFail,
43 |
44 | /// 正在刷新
45 | curRefreshing,
46 | }
47 |
48 | /// 分页数据对象
49 | class PagingDataModel {
50 | // 当前页码
51 | int curPage;
52 |
53 | // 总共多少页
54 | int pageCount;
55 |
56 | // 总共 数据数量
57 | int total;
58 |
59 | // 当前页 数据数量
60 | int size;
61 |
62 | /// 响应的完整数据
63 | /// 你可能还需要,响应数据的 其他字段,
64 | ///
65 | /// assert((){
66 | /// var mListModel = pageDataModel?.data as MessageListModel?; // 转化成对应的Model
67 | /// debugPrint('---pageCount:${mListModel?.pageCount}'); // 获取字段
68 | /// return true;
69 | /// }());
70 | dynamic data;
71 |
72 | // 分页参数 字段,一般情况都是固定的,以防万一
73 | String? curPageField;
74 |
75 | // 数据列表
76 | List listData = [];
77 |
78 | // 当前的PageDataModel
79 | DM? pageDataModel;
80 |
81 | // 当前的PageViewModel
82 | VM? pageViewModel;
83 |
84 | PagingBehavior pagingBehavior = PagingBehavior.idle;
85 |
86 | PagingState pagingState = PagingState.idle;
87 |
88 | // 记录之前列表的长度
89 | int originalListDataLength = 0;
90 |
91 | PagingDataModel(
92 | {this.curPage = 1,
93 | this.pageCount = 0,
94 | this.total = 0,
95 | this.size = 0,
96 | this.data,
97 | this.curPageField = 'curPage',
98 | this.pageDataModel}) :
99 | listData = [],
100 | originalListDataLength = 0;
101 |
102 | /// 这两个方法,由 RefreshLoadWidget 组件调用
103 |
104 | /// 加载更多,追加数据
105 | Future loadListData() async {
106 | PagingState pagingState = PagingState.curLoading;
107 | pagingBehavior = PagingBehavior.load;
108 | Map? param = {curPageField!: ++curPage};
109 | PageViewModel? currentPageViewModel = await pageViewModel?.requestData(params: param);
110 | if(currentPageViewModel?.pageDataModel?.type == NotifierResultType.success) {
111 | // 没有更多数据了
112 | if(currentPageViewModel?.pageDataModel?.total == listData.length ||
113 | originalListDataLength == listData.length) {
114 | curPage = curPage > 1 ? --curPage : 1;
115 | pagingState = PagingState.loadNoData;
116 | } else {
117 | pagingState = PagingState.loadSuccess;
118 | }
119 | originalListDataLength = listData.length;
120 | } else {
121 | curPage = curPage > 1 ? --curPage : 1;
122 | pagingState = PagingState.loadFail;
123 | }
124 | return pagingState;
125 | }
126 |
127 | /// 下拉刷新数据
128 | Future refreshListData() async {
129 | PagingState pagingState = PagingState.curRefreshing;
130 | pagingBehavior = PagingBehavior.refresh;
131 | curPage = 1;
132 | listData.clear();
133 | Map? param = {curPageField!: curPage};
134 | PageViewModel? currentPageViewModel = await pageViewModel?.requestData(params: param);
135 | if(currentPageViewModel?.pageDataModel?.type == NotifierResultType.success) {
136 | originalListDataLength = listData.length;
137 | pagingState = PagingState.refreshSuccess;
138 | } else {
139 | originalListDataLength = 0;
140 | pagingState = PagingState.refreshFail;
141 | }
142 |
143 | return pagingState;
144 | }
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/lib/common/repository/base_repository.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:dio/dio.dart';
4 | import 'package:flutter_develop_template/common/mvvm/base_model.dart';
5 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
6 | import 'package:flutter_develop_template/common/widget/notifier_widget.dart';
7 |
8 | import '../net/dio_response.dart';
9 |
10 | typedef JsonCoverEntity = T Function(Map json);
11 |
12 | class BaseRepository {
13 |
14 | /// 统一处理 响应数据,可以避免写 重复代码,但如果业务复杂,可能还是需要在原始写法上,扩展
15 |
16 | /// 普通页面(非分页)数据请求 统一处理
17 | Future httpPageRequest({
18 | required PageViewModel pageViewModel,
19 | required Future Function() future,
20 | required JsonCoverEntity jsonCoverEntity,
21 | CancelToken? cancelToken,
22 | int curPage = 1,
23 | }) async {
24 | try {
25 | Response response = await future();
26 |
27 | if (response.statusCode == REQUEST_SUCCESS) {
28 | /// 请求成功
29 | pageViewModel.pageDataModel?.type = NotifierResultType.success;
30 |
31 | /// ViewModel 和 Model 相互持有
32 | dynamic model = jsonCoverEntity(response.data);
33 | model.vm = pageViewModel;
34 | pageViewModel.pageDataModel?.data = model;
35 | } else {
36 | /// 请求成功,但业务不通过,比如没有权限
37 | pageViewModel.pageDataModel?.type = NotifierResultType.unauthorized;
38 | pageViewModel.pageDataModel?.errorMsg = response.statusMessage;
39 | }
40 | } on DioException catch (dioEx) {
41 | /// 请求异常
42 | pageViewModel.pageDataModel?.type = NotifierResultType.dioError;
43 | pageViewModel.pageDataModel?.errorMsg = dioErrorConversionText(dioEx);
44 | } catch (e) {
45 | /// 未知异常
46 | pageViewModel.pageDataModel?.type = NotifierResultType.fail;
47 | pageViewModel.pageDataModel?.errorMsg = e.toString();
48 | }
49 |
50 | return pageViewModel;
51 | }
52 |
53 | /// 分页数据请求 统一处理
54 | Future httpPagingRequest({
55 | required PageViewModel pageViewModel,
56 | required Future Function() future,
57 | required JsonCoverEntity jsonCoverEntity,
58 | CancelToken? cancelToken,
59 | int curPage = 1,
60 | }) async {
61 | try {
62 | Response response = await future();
63 |
64 | if (response.statusCode == REQUEST_SUCCESS) {
65 | /// 请求成功
66 | pageViewModel.pageDataModel?.type = NotifierResultType.success;
67 |
68 | /// 有分页
69 | pageViewModel.pageDataModel?.isPaging = true;
70 |
71 | /// 分页代码
72 | /// ViewModel 和 Model 相互持有代码,写着 correlationPaging() 里面
73 | pageViewModel.pageDataModel?.correlationPaging(
74 | pageViewModel,
75 | jsonCoverEntity(response.data) as dynamic,
76 | );
77 | } else {
78 | /// 请求成功,但业务不通过,比如没有权限
79 | pageViewModel.pageDataModel?.type = NotifierResultType.unauthorized;
80 | pageViewModel.pageDataModel?.errorMsg = response.statusMessage;
81 | }
82 | } on DioException catch (dioEx) {
83 | /// 请求异常
84 | pageViewModel.pageDataModel?.type = NotifierResultType.dioError;
85 | pageViewModel.pageDataModel?.errorMsg = dioErrorConversionText(dioEx);
86 | } catch (e) {
87 | /// 未知异常
88 | pageViewModel.pageDataModel?.type = NotifierResultType.fail;
89 | pageViewModel.pageDataModel?.errorMsg = e.toString();
90 | }
91 |
92 | return pageViewModel;
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/lib/common/util/global.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 |
4 | import '../../res/style/color_styles.dart';
5 |
6 | /// 状态栏样式
7 | SystemUiOverlayStyle _systemOverlayStyleForBrightness(Brightness brightness,
8 | [Color? backgroundColor]) {
9 | final SystemUiOverlayStyle style = brightness == Brightness.dark
10 | ? SystemUiOverlayStyle.light
11 | : SystemUiOverlayStyle.dark;
12 | // For backward compatibility, create an overlay style without system navigation bar settings.
13 | return SystemUiOverlayStyle(
14 | statusBarColor: backgroundColor,
15 | statusBarBrightness: style.statusBarBrightness,
16 | statusBarIconBrightness: style.statusBarIconBrightness,
17 | systemStatusBarContrastEnforced: style.systemStatusBarContrastEnforced,
18 | );
19 | }
20 |
21 | /// 下面 注解 没写反!!!,给白色,图标/字体 实际情况是 黑色,反之 给黑色,图标/字体 是 白色
22 |
23 | /// 状态栏 背景透明,图标/字体 黑色
24 | SystemUiOverlayStyle get overlayBlackStyle =>
25 | _systemOverlayStyleForBrightness(
26 | ThemeData.estimateBrightnessForColor(ColorStyles.color_FFFFFF), // 状态栏图标字体颜色
27 | ColorStyles.color_transparent, // 状态栏背景色
28 | );
29 |
30 | /// 状态栏 背景透明,图标/字体 白色
31 | SystemUiOverlayStyle get overlayWhiteStyle =>
32 | _systemOverlayStyleForBrightness(
33 | ThemeData.estimateBrightnessForColor(ColorStyles.color_000000), // 状态栏图标字体颜色
34 | ColorStyles.color_transparent, // 状态栏背景色
35 | );
--------------------------------------------------------------------------------
/lib/common/widget/global_notification_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// 在执行全局操作后,所有继承 BaseStatefulPageState 的子页面,
4 | /// 都会执行 didChangeDependencies() 方法,然后执行 build() 方法
5 | ///
6 | /// 具体原理:是 InheritedWidget 的特性
7 | /// https://loveky.github.io/2018/07/18/how-flutter-inheritedwidget-works/
8 |
9 | /// 全局操作类型
10 | enum GlobalOperate {
11 | /// 默认空闲
12 | idle,
13 |
14 | /// 切换登陆
15 | switchLogin,
16 |
17 | /// ... ...
18 | }
19 |
20 | /// 持有 全局操作状态 的 InheritedWidget
21 | class GlobalNotificationWidget extends InheritedWidget {
22 | GlobalNotificationWidget({
23 | required this.globalOperate,
24 | required super.child});
25 |
26 | final GlobalOperate globalOperate;
27 |
28 | static GlobalNotificationWidget? of(BuildContext context) {
29 | return context
30 | .dependOnInheritedWidgetOfExactType();
31 | }
32 |
33 | /// 通知所有建立依赖的 子Widget
34 | @override
35 | bool updateShouldNotify(covariant GlobalNotificationWidget oldWidget) =>
36 | oldWidget.globalOperate != globalOperate &&
37 | globalOperate != GlobalOperate.idle;
38 | }
39 |
40 | /// 具体使用的 全局操作 Widget
41 | ///
42 | /// 执行全局操作: GlobalOperateProvider.runGlobalOperate(context: context, operate: GlobalOperate.switchLogin);
43 | /// 获取全局操作类型 GlobalOperateProvider.getGlobalOperate(context: context)
44 | class GlobalOperateProvider extends StatefulWidget {
45 | const GlobalOperateProvider({super.key, required this.child});
46 |
47 | final Widget child;
48 |
49 | /// 执行全局操作
50 | static runGlobalOperate({
51 | required BuildContext? context,
52 | required GlobalOperate operate,
53 | }) {
54 | context
55 | ?.findAncestorStateOfType<_GlobalOperateProviderState>()
56 | ?._runGlobalOperate(operate: operate);
57 | }
58 |
59 | /// 获取全局操作类型
60 | static GlobalOperate? getGlobalOperate({required BuildContext? context}) {
61 | return context
62 | ?.findAncestorStateOfType<_GlobalOperateProviderState>()
63 | ?.globalOperate;
64 | }
65 |
66 | @override
67 | State createState() => _GlobalOperateProviderState();
68 | }
69 |
70 | class _GlobalOperateProviderState extends State {
71 | GlobalOperate globalOperate = GlobalOperate.idle;
72 |
73 | /// 执行全局操作
74 | _runGlobalOperate({required GlobalOperate operate}) {
75 | // 先重置
76 | globalOperate = GlobalOperate.idle;
77 |
78 | // 再赋值
79 | globalOperate = operate;
80 |
81 | /// 别忘了刷新,如果不刷新,子widget不会执行 didChangeDependencies 方法
82 | setState(() {});
83 | }
84 |
85 | @override
86 | Widget build(BuildContext context) {
87 | return GlobalNotificationWidget(
88 | globalOperate: globalOperate,
89 | child: widget.child,
90 | );
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/lib/common/widget/notifier_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_develop_template/common/mvvm/base_change_notifier.dart';
3 | import 'package:flutter_develop_template/common/paging/paging_data_model.dart';
4 | import '../../res/string/str_common.dart';
5 | import 'package:flutter_develop_template/main/application.dart';
6 |
7 | import '../mvvm/base_view_model.dart';
8 | import '../paging/base_paging_model.dart';
9 |
10 | enum NotifierResultType {
11 | // 不检查
12 | notCheck,
13 |
14 | // 加载中
15 | loading,
16 |
17 | // 请求成功
18 | success,
19 |
20 | // 这种属于请求成功,但业务不通过,比如没有权限
21 | unauthorized,
22 |
23 | // 请求异常
24 | dioError,
25 |
26 | // 未知异常
27 | fail,
28 | }
29 |
30 | class PageDataModel extends BaseChangeNotifier {
31 | // 完整的数据
32 | T? data;
33 |
34 | // 列表数据
35 | L? datas;
36 |
37 | // 当前页码
38 | int? curPage;
39 |
40 | // 总共多少页
41 | int? pageCount;
42 |
43 | // 当前页 数据数量
44 | int? size;
45 |
46 | // 总共 数据数量
47 | int? total;
48 |
49 | PagingDataModel? pagingDataModel;
50 |
51 | NotifierResultType type;
52 |
53 | String? errorMsg;
54 |
55 | // 是否有分页
56 | bool isPaging;
57 |
58 | PageDataModel({
59 | this.data,
60 | this.curPage = 1,
61 | this.pageCount = 0,
62 | this.size = 0,
63 | this.total = 0,
64 | this.pagingDataModel,
65 | this.type = NotifierResultType.loading,
66 | this.errorMsg,
67 | this.isPaging = false
68 | });
69 |
70 | /// 分页代码
71 |
72 | /// 这块是在继承 PageViewModel类 中使用的
73 | /// 刷新/加载更多 后,重新赋值最新的 分页数据
74 | void bindingPaging(
75 | PageViewModel currentPageViewModel,
76 | PageDataModel originalPageDataModel,
77 | PagingDataModel originalPagingDataModel,
78 | PageViewModel originalPageViewModel,
79 | ) {
80 | originalPageDataModel.pagingDataModel = originalPagingDataModel;
81 |
82 | var list = (currentPageViewModel.pageDataModel?.data.datas as List?) ?? [];
83 | originalPagingDataModel
84 | ..pageCount = currentPageViewModel.pageDataModel?.pageCount ?? 0
85 | ..total = currentPageViewModel.pageDataModel?.total ?? 0
86 | ..size = currentPageViewModel.pageDataModel?.size ?? 0
87 | ..data = currentPageViewModel.pageDataModel
88 | ..listData.addAll(list)
89 | ..pageDataModel = originalPageDataModel
90 | ..pageViewModel = originalPageViewModel;
91 | }
92 |
93 |
94 | /// 这块是在 请求接口方法里 中使用的
95 | /// 将请求的数据 分页数据,赋值给 PageDataModel
96 | void correlationPaging(
97 | PageViewModel pageViewModel,
98 | BasePagingModel data,
99 | ) {
100 |
101 | /// ViewModel 和 Model 相互持有
102 | data.vm = pageViewModel;
103 | pageViewModel.pageDataModel?.data = data;
104 |
105 | pageViewModel.pageDataModel?.pageCount = data.pageCount ?? 0;
106 | pageViewModel.pageDataModel?.size = data.size ?? 0;
107 | pageViewModel.pageDataModel?.total = data.total ?? 0;
108 | }
109 | }
110 |
111 | typedef NotifierPageWidgetBuilder = Widget
112 | Function(BuildContext context, PageDataModel model);
113 |
114 | /// 这个是配合 PageDataModel 类使用的
115 | class NotifierPageWidget extends StatefulWidget {
116 | NotifierPageWidget({
117 | super.key,
118 | required this.model,
119 | required this.builder,
120 | });
121 |
122 | /// 需要监听的数据观察类
123 | final PageDataModel? model;
124 |
125 | final NotifierPageWidgetBuilder builder;
126 |
127 | @override
128 | _NotifierPageWidgetState createState() => _NotifierPageWidgetState();
129 | }
130 |
131 | class _NotifierPageWidgetState
132 | extends State> {
133 | PageDataModel? model;
134 |
135 | /// 刷新UI
136 | refreshUI() => setState(() {
137 | model = widget.model;
138 | });
139 |
140 | /// 对数据进行绑定监听
141 | @override
142 | void initState() {
143 | super.initState();
144 |
145 | model = widget.model;
146 |
147 | // 先清空一次已注册的Listener,防止重复触发
148 | model?.removeListener(refreshUI);
149 |
150 | // 添加监听
151 | model?.addListener(refreshUI);
152 | }
153 |
154 | @override
155 | void didUpdateWidget(covariant NotifierPageWidget oldWidget) {
156 | super.didUpdateWidget(oldWidget);
157 | if (oldWidget.model != widget.model) {
158 | // 先清空一次已注册的Listener,防止重复触发
159 | oldWidget.model?.removeListener(refreshUI);
160 |
161 | model = widget.model;
162 |
163 | // 添加监听
164 | model?.addListener(refreshUI);
165 | }
166 | }
167 |
168 | @override
169 | Widget build(BuildContext context) {
170 |
171 | if (model?.type == NotifierResultType.notCheck) {
172 | return widget.builder(context, model!);
173 | }
174 |
175 | if (model?.type == NotifierResultType.loading) {
176 | return Center(
177 | child: Text(StrCommon.loading),
178 | );
179 | }
180 |
181 | if (model?.type == NotifierResultType.success) {
182 | if (model?.data == null) {
183 | return Center(
184 | child: Text(StrCommon.dataIsEmpty),
185 | );
186 | }
187 | if(model?.isPaging ?? false) {
188 | var lists = model?.data?.datas as List?;
189 | if(lists?.isEmpty ?? false){
190 | return Center(
191 | child: Text(StrCommon.listDataIsEmpty),
192 | );
193 | };
194 | }
195 | return widget.builder(context, model!);
196 | }
197 |
198 | if (model?.type == NotifierResultType.unauthorized) {
199 | return Center(
200 | child: Text('${StrCommon.businessDoesNotPass}:${model?.errorMsg}'),
201 | );
202 | }
203 |
204 | /// 异常抛出,会在终端会显示,可帮助开发阶段,快速定位异常所在,
205 | /// 但会阻断,后续代码执行,建议 非开发阶段 关闭
206 | if(EnvConfig.throwError) {
207 | throw Exception('${model?.errorMsg}');
208 | }
209 |
210 | if (model?.type == NotifierResultType.dioError) {
211 | return Center(
212 | child: Text('${StrCommon.dioErrorAnomaly}:${model?.errorMsg}'),
213 | );
214 | }
215 |
216 | if (model?.type == NotifierResultType.fail) {
217 | return Center(
218 | child: Text('${StrCommon.unknownAnomaly}:${model?.errorMsg}'),
219 | );
220 | }
221 |
222 | return Center(
223 | child: Text('${StrCommon.pleaseService}:${model?.errorMsg}'),
224 | );
225 | }
226 |
227 | @override
228 | void dispose() {
229 | widget.model?.removeListener(refreshUI);
230 | super.dispose();
231 | }
232 | }
233 |
234 | typedef NotifierValueWidgetBuilder = Widget Function(BuildContext context, T? value);
235 | /// 这个是配合 继承 BaseChangeNotifier 类使用的
236 | class NotifierWidget extends StatefulWidget {
237 | const NotifierWidget({
238 | super.key,
239 | required this.data,
240 | required this.builder,
241 | });
242 |
243 | /// 需要监听的数据观察类
244 | final T? data;
245 |
246 | final Widget Function(BuildContext, T?) builder;
247 |
248 | @override
249 | State> createState() => _NotifierWidgetState();
250 | }
251 |
252 | class _NotifierWidgetState
253 | extends State> {
254 | T? data;
255 |
256 | /// 刷新UI
257 | refreshState() => setState(() {
258 | data = widget.data;
259 | });
260 |
261 | /// 对数据进行绑定监听
262 | @override
263 | void initState() {
264 | super.initState();
265 |
266 | data = widget.data;
267 |
268 | // 先清空一次已注册的Listener,防止重复触发
269 | data?.removeListener(refreshState);
270 |
271 | // 添加监听
272 | data?.addListener(refreshState);
273 | }
274 |
275 | @override
276 | void didUpdateWidget(covariant NotifierWidget oldWidget) {
277 | super.didUpdateWidget(oldWidget);
278 | if (oldWidget.data != widget.data) {
279 | // 先清空一次已注册的Listener,防止重复触发
280 | oldWidget.data?.removeListener(refreshState);
281 |
282 | data = widget.data;
283 |
284 | // 添加监听
285 | data?.addListener(refreshState);
286 | }
287 | }
288 |
289 | @override
290 | Widget build(BuildContext context) {
291 | if (data == null) {
292 | return Center(
293 | child: Text(StrCommon.loading),
294 | );
295 | }
296 | return widget.builder(context, data);
297 | }
298 |
299 | @override
300 | void dispose() {
301 | widget.data?.removeListener(refreshState);
302 | super.dispose();
303 | }
304 | }
--------------------------------------------------------------------------------
/lib/common/widget/refresh_load_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_develop_template/common/paging/paging_data_model.dart';
3 | import 'package:pull_to_refresh/pull_to_refresh.dart';
4 |
5 | import '../../res/string/str_common.dart';
6 |
7 | /// 封装 下拉刷新/上拉加载更多 组件
8 | class RefreshLoadWidget extends StatefulWidget {
9 | const RefreshLoadWidget({
10 | super.key,
11 | required this.pagingDataModel,
12 | required this.scrollView,
13 | this.header,
14 | this.footer,
15 | this.enablePullDown = true,
16 | this.enablePullUp = true,
17 | });
18 |
19 | final PagingDataModel pagingDataModel;
20 | final ScrollView scrollView;
21 | final Widget? header;
22 | final Widget? footer;
23 |
24 | // 开启/关闭 下拉刷新
25 | final bool enablePullDown;
26 |
27 | // 开启/关闭 上拉加载
28 | final bool enablePullUp;
29 |
30 | @override
31 | State createState() => _RefreshLoadWidgetState();
32 | }
33 |
34 | class _RefreshLoadWidgetState extends State {
35 | late RefreshController refreshController;
36 |
37 | @override
38 | void initState() {
39 | super.initState();
40 | refreshController = RefreshController();
41 | }
42 |
43 | @override
44 | void dispose() {
45 | refreshController.dispose();
46 | super.dispose();
47 | }
48 |
49 | /// 下拉刷新
50 | onRefresh() async {
51 | // 正在刷新
52 | if (widget.pagingDataModel.pagingState == PagingState.curRefreshing) {
53 | return;
54 | }
55 |
56 | PagingState resultType = await widget.pagingDataModel.refreshListData();
57 | if (resultType == PagingState.refreshSuccess) {
58 | refreshController.refreshCompleted();
59 | } else {
60 | refreshController.refreshFailed();
61 | }
62 |
63 | // 重置空闲状态
64 | widget.pagingDataModel.pagingState = PagingState.idle;
65 | widget.pagingDataModel.pagingBehavior = PagingBehavior.idle;
66 | }
67 |
68 | /// 上拉加载
69 | onLoading() async {
70 | // 正在加载
71 | if (widget.pagingDataModel.pagingState == PagingState.curLoading) {
72 | return;
73 | }
74 |
75 | PagingState resultType = await widget.pagingDataModel.loadListData();
76 | if (resultType == PagingState.loadSuccess) {
77 | refreshController.loadComplete();
78 | } else if (resultType == PagingState.loadNoData) {
79 | refreshController.loadNoData();
80 | } else {
81 | refreshController.loadFailed();
82 | }
83 |
84 | // 重置空闲状态
85 | widget.pagingDataModel.pagingState = PagingState.idle;
86 | widget.pagingDataModel.pagingBehavior = PagingBehavior.idle;
87 | }
88 |
89 | @override
90 | Widget build(BuildContext context) {
91 | return SmartRefresher(
92 | controller: refreshController,
93 | // 下拉刷新
94 | enablePullDown: true,
95 | // 上拉加载
96 | enablePullUp: true,
97 | header: widget.header ??
98 | ClassicHeader(
99 | idleText: StrCommon.pullDownToRefresh,
100 | refreshingText: StrCommon.refreshing,
101 | completeText: StrCommon.loadedSuccess,
102 | releaseText: StrCommon.releaseToRefreshImmediately,
103 | failedText: StrCommon.refreshFailed,
104 | ),
105 | footer: widget.footer ??
106 | ClassicFooter(
107 | idleText: StrCommon.pullUpLoad,
108 | loadingText: StrCommon.loading,
109 | canLoadingText: StrCommon.letGoLoading,
110 | failedText: StrCommon.failedLoad,
111 | noDataText: StrCommon.noMoreData,
112 | // 没有内容的文字
113 | noMoreIcon: Icon(Icons.data_array), // 没有内容的图标
114 | ),
115 | onRefresh: onRefresh,
116 | onLoading: onLoading,
117 | child: widget.scrollView,
118 | );
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_develop_template/main/application.dart';
2 |
3 | /// 生产环境 入口函数
4 | void main() => Application.runApplication(
5 | envTag: EnvTag.prod, // 生产环境
6 | platform: ApplicationPlatform.app, // 手机应用
7 | isGlobalNotification: true, // 是否有全局通知操作,比如切换用户
8 | baseUrl: 'https://www.wanandroid.com/', // 域名
9 | );
10 |
--------------------------------------------------------------------------------
/lib/main/app.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_develop_template/common/mvvm/base_page.dart';
5 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
6 | import '../../router/page_route_observer.dart';
7 | import '../../router/routers.dart';
8 | import 'package:flutter_develop_template/common/widget/global_notification_widget.dart';
9 | import 'package:flutter_develop_template/module/message/view/message_v.dart';
10 |
11 | import '../../res/style/color_styles.dart';
12 | import '../../res/style/theme_styles.dart';
13 | import '../module/home/view/home_v.dart';
14 | import '../module/order/view/order_v.dart';
15 | import '../module/personal/view/personal_v.dart';
16 |
17 | /// App初始化的第一个页面可能是 其他页面,比如 广告、引导页、登陆页面
18 | enum AppInitState {
19 | /// 是App主体页面
20 | isAppMainPage,
21 |
22 | /// 不是主体页面
23 | noAppMainPage
24 | }
25 |
26 | /// 全局key
27 | /// 获取全局context方式:navigatorKey.currentContext
28 | final GlobalKey navigatorKey = GlobalKey();
29 |
30 | /// 这个对象 可以获取当前设配信息
31 | MediaQueryData? media;
32 |
33 | /// 监听全局路由,比如获取 当前路由栈里 页面总数
34 | PageRouteObserver? pageRouteObserver;
35 |
36 | class App extends StatelessWidget {
37 | const App({super.key});
38 |
39 | @override
40 | Widget build(BuildContext context) {
41 |
42 | /// 初始化 MediaQuery、PageRouteObserver
43 | media ??= MediaQuery.of(context);
44 | pageRouteObserver ??= PageRouteObserver();
45 |
46 | return GlobalOperateProvider(
47 | child: MaterialApp(
48 | title: 'Flutter Demo',
49 | debugShowCheckedModeBanner: false,
50 | navigatorObservers: [pageRouteObserver!],
51 | // 全局key
52 | navigatorKey: navigatorKey,
53 | // 使用路由找不到页面时,就会执行 onGenerateRoute
54 | onGenerateRoute: Routers.router.generator,
55 | theme: ThemeStyles.defaultTheme,
56 | // PopScope:监听返回键
57 | home: PopScope(
58 | // WillPopScope 3.16中过时,使用PopScope替换
59 | canPop: false,
60 | onPopInvoked: (bool didPop) {
61 | if (didPop) {
62 | return;
63 | }
64 |
65 | // 这里使用 Navigator.of(context).pop(); 是无效的
66 | // 使用SystemNavigator.pop() 或者 exit()
67 |
68 | // SystemNavigator.pop():用于在导航堆栈中弹出最顶层的页面,并在导航堆栈为空时退出应用程序
69 | // 平台兼容性:如果你只用Flutter做Android应用,优先使用 SystemNavigator.pop() 来退出应用程序。
70 |
71 | // exit():直接终止应用程序的运行。
72 | // 平台兼容性:适用于所有平台(Android、iOS等)
73 |
74 | // 写逻辑代码
75 | // ...
76 |
77 | exit(0); // 退出应用
78 | },
79 | child: AppTransfer(
80 | initState: AppInitState.isAppMainPage,
81 | )),
82 | ),
83 | );
84 | }
85 | }
86 |
87 | /// 这个是用来中转的,比如初始化第一个启动的页面 可能是 广告、引导页、登陆页面,之后再从这些页面进入 App主体页面
88 | class AppTransfer extends StatelessWidget {
89 | const AppTransfer({super.key, required this.initState});
90 |
91 | final AppInitState initState;
92 |
93 | @override
94 | Widget build(BuildContext context) {
95 | Widget child; // MaterialApp
96 | switch (initState) {
97 | case AppInitState.isAppMainPage:
98 | {
99 | // 先判断是否登陆
100 | // ... ...
101 | child = AppMainPage();
102 | }
103 | break;
104 | default:
105 | // 进入 广告、引导页 等等,再从这些页面进入 App首页
106 | child = AppMainPage();
107 | }
108 | return child;
109 | }
110 | }
111 |
112 | /// 这是App主体页面,主要是 PageView + BottomNavigationBar
113 | class AppMainPage extends BaseStatefulPage {
114 | AppMainPage({super.key});
115 |
116 | @override
117 | AppMainPageState createState() => AppMainPageState();
118 | }
119 |
120 | class AppMainPageState extends BaseStatefulPageState {
121 |
122 | PageController? pageController;
123 |
124 | @override
125 | void initAttribute() {
126 | pageController ??= PageController(
127 | initialPage: 0,
128 | keepPage: true,
129 | );
130 | }
131 |
132 | @override
133 | void initObserver() {
134 |
135 | }
136 |
137 | @override
138 | AppMainPageViewModel viewBindingViewModel() {
139 | /// ViewModel 和 View 相互持有
140 | return AppMainPageViewModel()..viewState = this;
141 | }
142 |
143 | @override
144 | void dispose() {
145 | pageController?.dispose();
146 | super.dispose();
147 | }
148 |
149 | void pageChanged(int index) {
150 | bottomSelectedIndex = index;
151 | }
152 |
153 | void bottomTap(int index) {
154 | bottomSelectedIndex = index;
155 | pageController?.jumpToPage(index);
156 | setState(() {});
157 | }
158 |
159 | int bottomSelectedIndex = 0;
160 |
161 | List buildBottomNavBarItems() {
162 | return [
163 | BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
164 | BottomNavigationBarItem(icon: Icon(Icons.message), label: 'Message'),
165 | BottomNavigationBarItem(icon: Icon(Icons.add_chart), label: 'Order'),
166 | BottomNavigationBarItem(icon: Icon(Icons.person_rounded), label: 'Personal'),
167 | ];
168 | }
169 |
170 | Widget buildPageView() {
171 | return PageView(
172 | physics: NeverScrollableScrollPhysics(), // 禁止滑动
173 | controller: pageController,
174 | onPageChanged: pageChanged,
175 | children: [
176 | HomeView(),
177 | MessageView(),
178 | OrderView(),
179 | PersonalView(),
180 | ],
181 | );
182 | }
183 |
184 | @override
185 | Widget appBuild(BuildContext context) {
186 | return Scaffold(
187 | body: buildPageView(),
188 | bottomNavigationBar: BottomNavigationBar(
189 | backgroundColor: ColorStyles.color_FFFFFF,
190 | type: BottomNavigationBarType.fixed, // 自适应宽度,但同时会失去,图标/文字 缩放效果
191 | currentIndex: bottomSelectedIndex,
192 | onTap: bottomTap,
193 | items: buildBottomNavBarItems(),
194 | unselectedItemColor: ColorStyles.color_1E88E5, // 未选中状态下的颜色
195 | unselectedFontSize: 14, // 未选中状态下的字体大小
196 | selectedItemColor: ColorStyles.color_EA5034, // 选中状态下的颜色
197 | selectedFontSize: 14, // 选中状态下的字体大小
198 | ),
199 | );
200 | }
201 |
202 | }
203 |
204 | class AppMainPageViewModel extends PageViewModel {
205 |
206 | @override
207 | onCreate() {
208 |
209 | }
210 |
211 | @override
212 | Future requestData({Map? params}) {
213 | return Future.value(null);
214 | }
215 |
216 | }
217 |
--------------------------------------------------------------------------------
/lib/main/application.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:sentry_flutter/sentry_flutter.dart';
5 | import '../../res/string/str_common.dart';
6 |
7 | import '../../router/routers.dart';
8 | import 'app.dart';
9 |
10 | enum ApplicationPlatform {
11 | /// 手机应用
12 | app,
13 |
14 | /// Web
15 | web,
16 |
17 | /// PC应用
18 | pc
19 | }
20 |
21 | enum EnvTag {
22 | /// 开发
23 | dev,
24 |
25 | /// 预发
26 | pre,
27 |
28 | /// 生产
29 | prod,
30 | }
31 |
32 | /// 环境配置实体
33 | class EnvConfig {
34 | /// 域名
35 | static String baseUrl = '';
36 |
37 | /// 开发环境
38 | static EnvTag envTag = EnvTag.dev;
39 |
40 | /// 是否开启抓包
41 | static bool proxyEnable = false;
42 |
43 | /// 抓包工具的代理地址 + 端口
44 | static String? caughtAddress;
45 |
46 | /// 平台
47 | static ApplicationPlatform platform = ApplicationPlatform.app;
48 |
49 | /// 是否有全局通知操作,比如切换用户
50 | static bool isGlobalNotification = false;
51 |
52 | /// 异常抛出,会在终端会显示,可帮助开发阶段,快速定位异常所在
53 | /// 但会阻断,后续代码执行,建议 非开发阶段 关闭
54 | static bool throwError = false;
55 |
56 | /// 是否开启 异常上报到 Sentry
57 | static bool pushErrToSentry = false;
58 |
59 | /// Sentry DNS 标识
60 | static String? sentryDNS;
61 | }
62 |
63 | class Application {
64 | Application.runApplication(
65 | {required EnvTag envTag, // 开发环境
66 | required String baseUrl, // 域名
67 | required ApplicationPlatform platform, // 平台
68 | bool proxyEnable = false, // 是否开启抓包
69 | String? caughtAddress, // 抓包工具的代理地址 + 端口
70 | bool isGlobalNotification = false, // 是否有全局通知操作,比如切换用户
71 | bool throwError = false, // 异常抛出,会在终端会显示,可帮助开发阶段,快速定位异常所在,但会阻断,后续代码执行
72 | bool pushErrToSentry = false, // 是否开启 异常上报到 Sentry
73 | String? sentryDNS // Sentry DNS 标识
74 | }) {
75 | EnvConfig.envTag = envTag;
76 | EnvConfig.baseUrl = baseUrl;
77 | EnvConfig.platform = platform;
78 | EnvConfig.proxyEnable = proxyEnable;
79 | EnvConfig.caughtAddress = caughtAddress;
80 | EnvConfig.isGlobalNotification = isGlobalNotification;
81 | EnvConfig.throwError = throwError;
82 | EnvConfig.pushErrToSentry = pushErrToSentry;
83 | EnvConfig.sentryDNS = sentryDNS;
84 |
85 | /// 确保一些依赖,全部初始化
86 | WidgetsFlutterBinding.ensureInitialized();
87 |
88 | /// 初始化路由
89 | Routers.configureRouters();
90 |
91 | /// Flutter 框架中 Widget显示错误时,替换为当前组件
92 | ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails) {
93 | return Material(
94 | child: Center(
95 | child: EnvConfig.envTag == EnvTag.dev
96 | ? Text(flutterErrorDetails.exceptionAsString())
97 | : Text(StrCommon.pleaseService),
98 | ),
99 | );
100 | };
101 |
102 | /// FlutterError.onError 是 Flutter 提供的一个全局错误处理回调,
103 | /// 用于捕获框架内发生的未处理异常。无论是同步还是异步异常,
104 | /// 只要它们发生在 Flutter 框架的上下文中,都会触发这个回调。
105 | FlutterError.onError = (FlutterErrorDetails flutterErrorDetails) async {
106 | if (EnvConfig.pushErrToSentry) {
107 |
108 | assert((){
109 | debugPrint('执行了异常 上报:${flutterErrorDetails.exception}');
110 | return true;
111 | }());
112 |
113 | /// 使用第三方服务(例如Sentry)上报错误
114 | /// Sentry.captureException(error, stackTrace: stackTrace);
115 | await Sentry.captureException(
116 | flutterErrorDetails.exception,
117 | stackTrace: flutterErrorDetails.stack,
118 | );
119 | }
120 | };
121 |
122 | /// runZonedGuarded 捕获 Flutter 框架中的 异步异常
123 | /// 注意:它不是全局的,只能捕获指定范围
124 | runZonedGuarded(() async {
125 | if (EnvConfig.pushErrToSentry) {
126 | /// 初始化 Sentry
127 | await SentryFlutter.init(
128 | (options) {
129 | options.dsn = EnvConfig.sentryDNS;
130 | options.tracesSampleRate = 1.0;
131 |
132 | if (EnvConfig.envTag == EnvTag.dev) {
133 | /// 是否打印输出 Sentry 日志
134 | options.debug = true;
135 | }
136 | },
137 | appRunner: () => runApp(App()),
138 | );
139 | } else {
140 | runApp(App());
141 | }
142 | }, (Object error, StackTrace stack) async {
143 | if (EnvConfig.pushErrToSentry) {
144 |
145 | assert((){
146 | debugPrint('执行了异常 上报:$error');
147 | return true;
148 | }());
149 |
150 | /// 使用第三方服务(例如Sentry)上报错误
151 | /// Sentry.captureException(error, stackTrace: stackTrace);
152 | await Sentry.captureException(
153 | error,
154 | stackTrace: stack,
155 | );
156 | }
157 | });
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/lib/main/main_dev.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_develop_template/main/application.dart';
2 |
3 | /// 开发环境 入口函数
4 | void main() => Application.runApplication(
5 | envTag: EnvTag.dev, // 开发环境
6 | platform: ApplicationPlatform.app, // 手机应用
7 | baseUrl: 'https://www.wanandroid.com/', // 域名
8 | proxyEnable: false, // 是否开启抓包
9 | caughtAddress: '192.168.1.3:8888', // 抓包工具的代理地址 + 端口
10 | isGlobalNotification: true, // 是否有全局通知操作,比如切换用户
11 | /// 异常抛出,会在终端会显示,可帮助开发阶段,快速定位异常所在,
12 | /// 但会阻断,后续代码执行,建议 非开发阶段 关闭
13 | throwError: true,
14 | pushErrToSentry: false, // 是否开启 异常上报到 Sentry
15 | sentryDNS: 'https://123456789191111@xxx.com/2' // Sentry DNS 标识
16 |
17 | /// AndroidManifest.xml 中
18 | /// 开启网络权限
19 | ///
20 | ///
21 | /// 如果使用 http,还需要配置
22 | /// Application.runApplication(
5 | envTag: EnvTag.pre, // 预发布环境
6 | platform: ApplicationPlatform.app, // 手机应用
7 | isGlobalNotification: true, // 是否有全局通知操作,比如切换用户
8 | baseUrl: 'https://www.wanandroid.com/', // 域名
9 | );
10 |
--------------------------------------------------------------------------------
/lib/module/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/lib/module/.DS_Store
--------------------------------------------------------------------------------
/lib/module/home/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/lib/module/home/.DS_Store
--------------------------------------------------------------------------------
/lib/module/home/api/home_repository.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:dio/dio.dart';
4 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
5 | import 'package:flutter_develop_template/common/net/dio_client.dart';
6 |
7 | import '../../../common/repository/base_repository.dart';
8 | import '../model/home_list_m.dart';
9 |
10 | class HomeRepository extends BaseRepository {
11 | /// 获取首页数据
12 | Future getHomeData({
13 | required PageViewModel pageViewModel,
14 | CancelToken? cancelToken,
15 | int curPage = 1,
16 | }) async =>
17 | httpPageRequest(
18 | pageViewModel: pageViewModel,
19 | jsonCoverEntity: HomeListModel.fromJson,
20 | future: () => DioClient().doGet('project/list/$curPage/json?cid=294', cancelToken: cancelToken),
21 | cancelToken: cancelToken,
22 | curPage: curPage);
23 |
24 | /// 这是不使用 httpPageRequest 的原始写法,如果业务复杂,可能还是需要在原始写法上,扩展
25 | /// 获取首页数据
26 | // Future getHomeData({
27 | // required PageViewModel pageViewModel,
28 | // CancelToken? cancelToken,
29 | // int curPage = 1,
30 | // }) async {
31 | // try {
32 | // Response response = await DioClient().doGet(
33 | // 'project/list/$curPage/json?cid=294',
34 | // cancelToken: cancelToken);
35 | //
36 | // if (response.statusCode == REQUEST_SUCCESS) {
37 | // /// 请求成功
38 | // pageViewModel.pageDataModel?.type = NotifierResultType.success;
39 | //
40 | // /// ViewModel 和 Model 相互持有
41 | // HomeListModel model = HomeListModel.fromJson(response.data);
42 | // model.vm = pageViewModel;
43 | // pageViewModel.pageDataModel?.data = model;
44 | // } else {
45 | // /// 请求成功,但业务不通过,比如没有权限
46 | // pageViewModel.pageDataModel?.type = NotifierResultType.unauthorized;
47 | // pageViewModel.pageDataModel?.errorMsg = response.statusMessage;
48 | // }
49 | // } on DioException catch (dioEx) {
50 | // /// 请求异常
51 | // pageViewModel.pageDataModel?.type = NotifierResultType.dioError;
52 | // pageViewModel.pageDataModel?.errorMsg = dioErrorConversionText(dioEx);
53 | // } catch (e) {
54 | // /// 未知异常
55 | // pageViewModel.pageDataModel?.type = NotifierResultType.fail;
56 | // pageViewModel.pageDataModel?.errorMsg = e.toString();
57 | // }
58 | //
59 | // return pageViewModel;
60 | // }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/module/home/model/home_list_m.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_develop_template/common/mvvm/base_model.dart';
3 |
4 | class HomeListModel extends BaseModel {
5 | int? curPage;
6 | List? datas;
7 | int? offset;
8 | bool? over;
9 | int? pageCount;
10 | int? size;
11 | int? total;
12 |
13 | ValueNotifier tapNum = ValueNotifier(0); // 点击次数
14 |
15 | @override
16 | void onDispose() {
17 | tapNum.dispose();
18 | super.onDispose();
19 | }
20 |
21 | HomeListModel(
22 | {this.curPage,
23 | this.datas,
24 | this.offset,
25 | this.over,
26 | this.pageCount,
27 | this.size,
28 | this.total});
29 |
30 | HomeListModel.fromJson(Map json) {
31 | curPage = json['curPage'];
32 | if (json['datas'] != null) {
33 | datas = [];
34 | json['datas'].forEach((v) {
35 | datas!.add(new Datas.fromJson(v));
36 | });
37 | }
38 | offset = json['offset'];
39 | over = json['over'];
40 | pageCount = json['pageCount'];
41 | size = json['size'];
42 | total = json['total'];
43 | }
44 |
45 | Map toJson() {
46 | final Map data = new Map();
47 | data['curPage'] = this.curPage;
48 | if (this.datas != null) {
49 | data['datas'] = this.datas!.map((v) => v.toJson()).toList();
50 | }
51 | data['offset'] = this.offset;
52 | data['over'] = this.over;
53 | data['pageCount'] = this.pageCount;
54 | data['size'] = this.size;
55 | data['total'] = this.total;
56 | return data;
57 | }
58 | }
59 |
60 | class Datas {
61 | bool? adminAdd;
62 | String? apkLink;
63 | int? audit;
64 | String? author;
65 | bool? canEdit;
66 | int? chapterId;
67 | String? chapterName;
68 | bool? collect;
69 | int? courseId;
70 | String? desc;
71 | String? descMd;
72 | String? envelopePic;
73 | bool? fresh;
74 | String? host;
75 | int? id;
76 | bool? isAdminAdd;
77 | String? link;
78 | String? niceDate;
79 | String? niceShareDate;
80 | String? origin;
81 | String? prefix;
82 | String? projectLink;
83 | int? publishTime;
84 | int? realSuperChapterId;
85 | int? selfVisible;
86 | int? shareDate;
87 | String? shareUser;
88 | int? superChapterId;
89 | String? superChapterName;
90 | List? tags;
91 | String? title;
92 | int? type;
93 | int? userId;
94 | int? visible;
95 | int? zan;
96 |
97 | Datas(
98 | {this.adminAdd,
99 | this.apkLink,
100 | this.audit,
101 | this.author,
102 | this.canEdit,
103 | this.chapterId,
104 | this.chapterName,
105 | this.collect,
106 | this.courseId,
107 | this.desc,
108 | this.descMd,
109 | this.envelopePic,
110 | this.fresh,
111 | this.host,
112 | this.id,
113 | this.isAdminAdd,
114 | this.link,
115 | this.niceDate,
116 | this.niceShareDate,
117 | this.origin,
118 | this.prefix,
119 | this.projectLink,
120 | this.publishTime,
121 | this.realSuperChapterId,
122 | this.selfVisible,
123 | this.shareDate,
124 | this.shareUser,
125 | this.superChapterId,
126 | this.superChapterName,
127 | this.tags,
128 | this.title,
129 | this.type,
130 | this.userId,
131 | this.visible,
132 | this.zan});
133 |
134 | Datas.fromJson(Map json) {
135 | adminAdd = json['adminAdd'];
136 | apkLink = json['apkLink'];
137 | audit = json['audit'];
138 | author = json['author'];
139 | canEdit = json['canEdit'];
140 | chapterId = json['chapterId'];
141 | chapterName = json['chapterName'];
142 | collect = json['collect'];
143 | courseId = json['courseId'];
144 | desc = json['desc'];
145 | descMd = json['descMd'];
146 | envelopePic = json['envelopePic'];
147 | fresh = json['fresh'];
148 | host = json['host'];
149 | id = json['id'];
150 | isAdminAdd = json['isAdminAdd'];
151 | link = json['link'];
152 | niceDate = json['niceDate'];
153 | niceShareDate = json['niceShareDate'];
154 | origin = json['origin'];
155 | prefix = json['prefix'];
156 | projectLink = json['projectLink'];
157 | publishTime = json['publishTime'];
158 | realSuperChapterId = json['realSuperChapterId'];
159 | selfVisible = json['selfVisible'];
160 | shareDate = json['shareDate'];
161 | shareUser = json['shareUser'];
162 | superChapterId = json['superChapterId'];
163 | superChapterName = json['superChapterName'];
164 | if (json['tags'] != null) {
165 | tags = [];
166 | json['tags'].forEach((v) {
167 | tags!.add(new Tags.fromJson(v));
168 | });
169 | }
170 | title = json['title'];
171 | type = json['type'];
172 | userId = json['userId'];
173 | visible = json['visible'];
174 | zan = json['zan'];
175 | }
176 |
177 | Map toJson() {
178 | final Map data = new Map();
179 | data['adminAdd'] = this.adminAdd;
180 | data['apkLink'] = this.apkLink;
181 | data['audit'] = this.audit;
182 | data['author'] = this.author;
183 | data['canEdit'] = this.canEdit;
184 | data['chapterId'] = this.chapterId;
185 | data['chapterName'] = this.chapterName;
186 | data['collect'] = this.collect;
187 | data['courseId'] = this.courseId;
188 | data['desc'] = this.desc;
189 | data['descMd'] = this.descMd;
190 | data['envelopePic'] = this.envelopePic;
191 | data['fresh'] = this.fresh;
192 | data['host'] = this.host;
193 | data['id'] = this.id;
194 | data['isAdminAdd'] = this.isAdminAdd;
195 | data['link'] = this.link;
196 | data['niceDate'] = this.niceDate;
197 | data['niceShareDate'] = this.niceShareDate;
198 | data['origin'] = this.origin;
199 | data['prefix'] = this.prefix;
200 | data['projectLink'] = this.projectLink;
201 | data['publishTime'] = this.publishTime;
202 | data['realSuperChapterId'] = this.realSuperChapterId;
203 | data['selfVisible'] = this.selfVisible;
204 | data['shareDate'] = this.shareDate;
205 | data['shareUser'] = this.shareUser;
206 | data['superChapterId'] = this.superChapterId;
207 | data['superChapterName'] = this.superChapterName;
208 | if (this.tags != null) {
209 | data['tags'] = this.tags!.map((v) => v.toJson()).toList();
210 | }
211 | data['title'] = this.title;
212 | data['type'] = this.type;
213 | data['userId'] = this.userId;
214 | data['visible'] = this.visible;
215 | data['zan'] = this.zan;
216 | return data;
217 | }
218 | }
219 |
220 | class Tags {
221 | String? name;
222 | String? url;
223 |
224 | Tags({this.name, this.url});
225 |
226 | Tags.fromJson(Map json) {
227 | name = json['name'];
228 | url = json['url'];
229 | }
230 |
231 | Map toJson() {
232 | final Map data = new Map();
233 | data['name'] = this.name;
234 | data['url'] = this.url;
235 | return data;
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/lib/module/home/view/home_v.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_develop_template/common/mvvm/base_page.dart';
5 | import '../../../../res/string/str_home.dart';
6 | import 'package:flutter_develop_template/common/widget/notifier_widget.dart';
7 | import 'package:flutter_develop_template/module/home/view_model/home_vm.dart';
8 |
9 | import '../../../../res/string/str_common.dart';
10 | import '../../../../res/style/color_styles.dart';
11 | import '../../../../res/style/text_styles.dart';
12 | import '../../../common/widget/global_notification_widget.dart';
13 | import '../model/home_list_m.dart';
14 |
15 | class HomeView extends BaseStatefulPage {
16 | HomeView({super.key});
17 |
18 | @override
19 | HomeViewState createState() => HomeViewState();
20 | }
21 |
22 | class HomeViewState extends BaseStatefulPageState {
23 |
24 | @override
25 | HomeViewModel viewBindingViewModel() {
26 | /// ViewModel 和 View 相互持有
27 | return HomeViewModel()..viewState = this;
28 | }
29 |
30 | @override
31 | void initAttribute() {}
32 |
33 | @override
34 | void initObserver() {}
35 |
36 | @override
37 | void dispose() {
38 | assert((){
39 | debugPrint('HomeView.onDispose()');
40 | return true;
41 | }());
42 |
43 | /// BaseStatefulPageState的子类,重写 dispose()
44 | /// 一定要执行父类 dispose(),防止内存泄漏
45 | super.dispose();
46 | }
47 |
48 | bool executeSwitchLogin = false;
49 |
50 | @override
51 | void didChangeDependencies() {
52 | var operate = GlobalOperateProvider.getGlobalOperate(context: context);
53 |
54 | assert((){
55 | debugPrint('HomeView.didChangeDependencies --- $operate');
56 | return true;
57 | }());
58 |
59 | // 切换用户
60 | // 正常业务流程是:从本地存储,拿到当前最新的用户ID,请求接口,我这里偷了个懒 😄
61 | // 直接使用随机数,模拟 不同用户ID
62 | if (operate == GlobalOperate.switchLogin) {
63 | executeSwitchLogin = true;
64 |
65 | // 重新请求数据
66 | // 如果你想刷新的时候,显示loading,加上这两行
67 | viewModel?.pageDataModel?.type = NotifierResultType.loading;
68 | viewModel?.pageDataModel?.refreshState();
69 |
70 | viewModel?.requestData(params: {'curPage': Random().nextInt(20)});
71 | }
72 | }
73 |
74 | ValueNotifier tapNum = ValueNotifier(0);
75 |
76 | @override
77 | Widget appBuild(BuildContext context) {
78 | return Scaffold(
79 | appBar: AppBar(
80 | backgroundColor: AppBarTheme.of(context).backgroundColor,
81 | /// 局部刷新
82 | title: ValueListenableBuilder(
83 | valueListenable: tapNum,
84 | builder: (context, value, _) {
85 | return Text(
86 | '${StrHome.home}:$value',
87 | style: TextStyles.style_222222_20,
88 | );
89 | },
90 | ),
91 | actions: [
92 | IconButton(
93 | onPressed: () {
94 | tapNum.value += 1;
95 | },
96 | icon: Icon(Icons.add)),
97 | IconButton(
98 | onPressed: () {
99 | viewModel?.requestData(params: {'curPage': Random().nextInt(20)});
100 | },
101 | icon: Icon(Icons.refresh)),
102 | IconButton(
103 | onPressed: () {
104 | // 如果你想刷新的时候,显示loading,加上这两行
105 | viewModel?.pageDataModel?.type = NotifierResultType.loading;
106 | viewModel?.pageDataModel?.refreshState();
107 |
108 | viewModel?.requestData(params: {'curPage': Random().nextInt(20)});
109 | },
110 | icon: Icon(Icons.refresh_sharp))
111 | ],
112 | ),
113 | body: NotifierPageWidget(
114 | model: viewModel?.pageDataModel,
115 | builder: (context, dataModel) {
116 | final data = dataModel.data as HomeListModel?;
117 | if(data != null) {
118 | /// 延迟一帧
119 | WidgetsBinding.instance.addPostFrameCallback((_){
120 | /// 赋值、并替换 HomeListModel 内的tapNum,建立联系
121 | tapNum.value = data.pageCount ?? 0;
122 | data.tapNum = tapNum;
123 | });
124 | }
125 | return Stack(
126 | children: [
127 | ListView.builder(
128 | padding: EdgeInsets.zero,
129 | itemCount: data?.datas?.length ?? 0,
130 | itemBuilder: (context, index) {
131 | return Container(
132 | width: MediaQuery.of(context).size.width,
133 | height: 50,
134 | alignment: Alignment.center,
135 | child: Text('${data?.datas?[index].title}'),
136 | );
137 | }),
138 | Container(
139 | color: ColorStyles.color_388E3C,
140 | child: executeSwitchLogin
141 | ? Row(
142 | mainAxisAlignment: MainAxisAlignment.center,
143 | children: [
144 | Text(StrCommon.executeSwitchUser),
145 | IconButton(
146 | onPressed: () {
147 | executeSwitchLogin = false;
148 | setState(() {});
149 | },
150 | icon: Icon(Icons.close))
151 | ],
152 | )
153 | : SizedBox(),
154 | ),
155 | ],
156 | );
157 | }
158 | ),
159 | );
160 | }
161 |
162 | /// 是否保存页面状态
163 | @override
164 | bool get wantKeepAlive => true;
165 |
166 | }
167 |
--------------------------------------------------------------------------------
/lib/module/home/view_model/home_vm.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
4 | import 'package:flutter_develop_template/common/widget/notifier_widget.dart';
5 | import 'package:flutter_develop_template/module/home/view/home_v.dart';
6 |
7 | import '../api/home_repository.dart';
8 |
9 | class HomeViewModel extends PageViewModel {
10 | CancelToken? cancelToken;
11 |
12 | @override
13 | onCreate() {
14 |
15 | assert((){
16 | /// 拿到 页面状态里的 对象、属性 等等
17 | debugPrint('---executeSwitchLogin:${state.executeSwitchLogin}');
18 | return true;
19 | }());
20 |
21 | cancelToken = CancelToken();
22 | pageDataModel = PageDataModel();
23 | requestData();
24 | }
25 |
26 | @override
27 | onDispose() {
28 | if (!(cancelToken?.isCancelled ?? true)) {
29 | cancelToken?.cancel();
30 | }
31 | assert((){
32 | debugPrint('HomeViewModel.onDispose()');
33 | return true;
34 | }());
35 |
36 | /// 别忘了执行父类的 onDispose
37 | super.onDispose();
38 | }
39 |
40 | /// 请求数据
41 | @override
42 | Future requestData({Map? params}) async {
43 | PageViewModel viewModel = await HomeRepository().getHomeData(
44 | pageViewModel: this,
45 | cancelToken: cancelToken,
46 | curPage: params?['curPage'] ?? 1
47 | );
48 | pageDataModel = viewModel.pageDataModel;
49 | pageDataModel?.refreshState();
50 | return Future.value(viewModel);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/lib/module/message/api/message_repository.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:flutter_develop_template/common/repository/base_repository.dart';
3 | import 'package:flutter_develop_template/common/net/dio_client.dart';
4 |
5 | import '../../../common/mvvm/base_view_model.dart';
6 | import '../model/message_list_m.dart';
7 |
8 | class MessageRepository extends BaseRepository {
9 |
10 | /// 分页列表
11 | Future getMessageData({
12 | required PageViewModel pageViewModel,
13 | CancelToken? cancelToken,
14 | int curPage = 1,
15 | }) async => httpPagingRequest(
16 | pageViewModel: pageViewModel,
17 | cancelToken: cancelToken,
18 | jsonCoverEntity: MessageListModel.fromJson,
19 | curPage: curPage,
20 | future: () => DioClient().doGet('article/list/$curPage/json', cancelToken: cancelToken));
21 |
22 |
23 | /// 这是不使用 httpPagingRequest 的原始写法,如果业务复杂,可能还是需要在原始写法上,扩展
24 | // /// 分页列表
25 | // Future getMessageData({
26 | // required PageViewModel pageViewModel,
27 | // CancelToken? cancelToken,
28 | // int curPage = 1,
29 | // }) async {
30 | // try {
31 | // Response response = await DioClient().doGet('article/list/$curPage/json', cancelToken: cancelToken);
32 | //
33 | // if(response.statusCode == REQUEST_SUCCESS) {
34 | // /// 请求成功
35 | // pageViewModel.pageDataModel?.type = NotifierResultType.success;
36 | //
37 | // /// 有分页
38 | // pageViewModel.pageDataModel?.isPaging = true;
39 | //
40 | // /// 分页代码
41 | // /// ViewModel 和 Model 相互持有代码,写着 correlationPaging() 里面
42 | // pageViewModel.pageDataModel?.correlationPaging(pageViewModel, MessageListModel.fromJson(response.data));
43 | // } else {
44 | //
45 | // /// 请求成功,但业务不通过,比如没有权限
46 | // pageViewModel.pageDataModel?.type = NotifierResultType.unauthorized;
47 | // pageViewModel.pageDataModel?.errorMsg = response.statusMessage;
48 | // }
49 | //
50 | // } on DioException catch (dioEx) {
51 | // /// 请求异常
52 | // pageViewModel.pageDataModel?.type = NotifierResultType.dioError;
53 | // pageViewModel.pageDataModel?.errorMsg = dioErrorConversionText(dioEx);
54 | // } catch (e) {
55 | // /// 未知异常
56 | // pageViewModel.pageDataModel?.type = NotifierResultType.fail;
57 | // pageViewModel.pageDataModel?.errorMsg = e.toString();
58 | // }
59 | //
60 | // return pageViewModel;
61 | // }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/lib/module/message/model/message_list_m.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_develop_template/common/paging/base_paging_model.dart';
2 |
3 | /// 这个实体有 分页列表数据集合,继承BasePagingModel
4 | class MessageListModel extends BasePagingModel {
5 | int? curPage;
6 | List? datas;
7 | int? offset;
8 | bool? over;
9 | int? pageCount;
10 | int? size;
11 | int? total;
12 |
13 | /// 别忘了这里,和父类相同的参数,传给父类
14 | /// 这种是语法糖写法,等同于下面的传统写法
15 | MessageListModel(
16 | {super.curPage,
17 | super.datas,
18 | super.offset,
19 | super.over,
20 | super.pageCount,
21 | super.size,
22 | super.total});
23 |
24 | /// 传统写法
25 | // MessageListModel({curPage, datas, offset, over, pageCount, size, total})
26 | // : super(
27 | // curPage: curPage,
28 | // datas: datas,
29 | // offset: offset,
30 | // over: over,
31 | // pageCount: pageCount,
32 | // size: size,
33 | // total: total);
34 |
35 | MessageListModel.fromJson(Map json) {
36 | curPage = json['curPage'];
37 | if (json['datas'] != null) {
38 | datas = [];
39 | json['datas'].forEach((v) {
40 | datas!.add(new Datas.fromJson(v));
41 | });
42 | }
43 | offset = json['offset'];
44 | over = json['over'];
45 | pageCount = json['pageCount'];
46 | size = json['size'];
47 | total = json['total'];
48 | }
49 |
50 | Map toJson() {
51 | final Map data = new Map();
52 | data['curPage'] = this.curPage;
53 | if (this.datas != null) {
54 | /// 这个实体是分页的列表项,继承BasePagingItem,要转化一下
55 | // data['datas'] = this.datas!.map((v) => v.toJson()).toList();
56 | data['datas'] = this.datas!.map((v) => (v as Datas).toJson()).toList();
57 | }
58 | data['offset'] = this.offset;
59 | data['over'] = this.over;
60 | data['pageCount'] = this.pageCount;
61 | data['size'] = this.size;
62 | data['total'] = this.total;
63 | return data;
64 | }
65 | }
66 |
67 | /// 这个实体是分页的列表项,继承BasePagingItem
68 | class Datas extends BasePagingItem {
69 | bool? adminAdd;
70 | String? apkLink;
71 | int? audit;
72 | String? author;
73 | bool? canEdit;
74 | int? chapterId;
75 | String? chapterName;
76 | bool? collect;
77 | int? courseId;
78 | String? desc;
79 | String? descMd;
80 | String? envelopePic;
81 | bool? fresh;
82 | String? host;
83 | int? id;
84 | bool? isAdminAdd;
85 | String? link;
86 | String? niceDate;
87 | String? niceShareDate;
88 | String? origin;
89 | String? prefix;
90 | String? projectLink;
91 | int? publishTime;
92 | int? realSuperChapterId;
93 | int? selfVisible;
94 | int? shareDate;
95 | String? shareUser;
96 | int? superChapterId;
97 | String? superChapterName;
98 | List? tags;
99 | String? title;
100 | int? type;
101 | int? userId;
102 | int? visible;
103 | int? zan;
104 |
105 | Datas(
106 | {this.adminAdd,
107 | this.apkLink,
108 | this.audit,
109 | this.author,
110 | this.canEdit,
111 | this.chapterId,
112 | this.chapterName,
113 | this.collect,
114 | this.courseId,
115 | this.desc,
116 | this.descMd,
117 | this.envelopePic,
118 | this.fresh,
119 | this.host,
120 | this.id,
121 | this.isAdminAdd,
122 | this.link,
123 | this.niceDate,
124 | this.niceShareDate,
125 | this.origin,
126 | this.prefix,
127 | this.projectLink,
128 | this.publishTime,
129 | this.realSuperChapterId,
130 | this.selfVisible,
131 | this.shareDate,
132 | this.shareUser,
133 | this.superChapterId,
134 | this.superChapterName,
135 | this.tags,
136 | this.title,
137 | this.type,
138 | this.userId,
139 | this.visible,
140 | this.zan});
141 |
142 | Datas.fromJson(Map json) {
143 | adminAdd = json['adminAdd'];
144 | apkLink = json['apkLink'];
145 | audit = json['audit'];
146 | author = json['author'];
147 | canEdit = json['canEdit'];
148 | chapterId = json['chapterId'];
149 | chapterName = json['chapterName'];
150 | collect = json['collect'];
151 | courseId = json['courseId'];
152 | desc = json['desc'];
153 | descMd = json['descMd'];
154 | envelopePic = json['envelopePic'];
155 | fresh = json['fresh'];
156 | host = json['host'];
157 | id = json['id'];
158 | isAdminAdd = json['isAdminAdd'];
159 | link = json['link'];
160 | niceDate = json['niceDate'];
161 | niceShareDate = json['niceShareDate'];
162 | origin = json['origin'];
163 | prefix = json['prefix'];
164 | projectLink = json['projectLink'];
165 | publishTime = json['publishTime'];
166 | realSuperChapterId = json['realSuperChapterId'];
167 | selfVisible = json['selfVisible'];
168 | shareDate = json['shareDate'];
169 | shareUser = json['shareUser'];
170 | superChapterId = json['superChapterId'];
171 | superChapterName = json['superChapterName'];
172 | if (json['tags'] != null) {
173 | tags = [];
174 | json['tags'].forEach((v) {
175 | tags!.add(new Tags.fromJson(v));
176 | });
177 | }
178 | title = json['title'];
179 | type = json['type'];
180 | userId = json['userId'];
181 | visible = json['visible'];
182 | zan = json['zan'];
183 | }
184 |
185 | Map toJson() {
186 | final Map data = new Map();
187 | data['adminAdd'] = this.adminAdd;
188 | data['apkLink'] = this.apkLink;
189 | data['audit'] = this.audit;
190 | data['author'] = this.author;
191 | data['canEdit'] = this.canEdit;
192 | data['chapterId'] = this.chapterId;
193 | data['chapterName'] = this.chapterName;
194 | data['collect'] = this.collect;
195 | data['courseId'] = this.courseId;
196 | data['desc'] = this.desc;
197 | data['descMd'] = this.descMd;
198 | data['envelopePic'] = this.envelopePic;
199 | data['fresh'] = this.fresh;
200 | data['host'] = this.host;
201 | data['id'] = this.id;
202 | data['isAdminAdd'] = this.isAdminAdd;
203 | data['link'] = this.link;
204 | data['niceDate'] = this.niceDate;
205 | data['niceShareDate'] = this.niceShareDate;
206 | data['origin'] = this.origin;
207 | data['prefix'] = this.prefix;
208 | data['projectLink'] = this.projectLink;
209 | data['publishTime'] = this.publishTime;
210 | data['realSuperChapterId'] = this.realSuperChapterId;
211 | data['selfVisible'] = this.selfVisible;
212 | data['shareDate'] = this.shareDate;
213 | data['shareUser'] = this.shareUser;
214 | data['superChapterId'] = this.superChapterId;
215 | data['superChapterName'] = this.superChapterName;
216 | if (this.tags != null) {
217 | data['tags'] = this.tags!.map((v) => v.toJson()).toList();
218 | }
219 | data['title'] = this.title;
220 | data['type'] = this.type;
221 | data['userId'] = this.userId;
222 | data['visible'] = this.visible;
223 | data['zan'] = this.zan;
224 | return data;
225 | }
226 | }
227 |
228 | class Tags {
229 | String? name;
230 | String? url;
231 |
232 | Tags({this.name, this.url});
233 |
234 | Tags.fromJson(Map json) {
235 | name = json['name'];
236 | url = json['url'];
237 | }
238 |
239 | Map toJson() {
240 | final Map data = new Map();
241 | data['name'] = this.name;
242 | data['url'] = this.url;
243 | return data;
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/lib/module/message/view/message_v.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_develop_template/common/mvvm/base_page.dart';
5 | import '../../../../res/string/str_common.dart';
6 | import '../../../../res/string/str_message.dart';
7 | import 'package:flutter_develop_template/common/widget/refresh_load_widget.dart';
8 | import 'package:flutter_develop_template/module/message/model/message_list_m.dart';
9 | import 'package:flutter_develop_template/module/message/view_model/message_vm.dart';
10 |
11 | import '../../../../res/style/color_styles.dart';
12 | import '../../../../res/style/text_styles.dart';
13 | import '../../../common/widget/global_notification_widget.dart';
14 | import '../../../common/widget/notifier_widget.dart';
15 |
16 | class MessageView extends BaseStatefulPage {
17 | MessageView({super.key});
18 |
19 | @override
20 | MessageViewState createState() => MessageViewState();
21 | }
22 |
23 | class MessageViewState extends BaseStatefulPageState {
24 |
25 | @override
26 | MessageViewModel viewBindingViewModel() {
27 | /// ViewModel 和 View 相互持有
28 | return MessageViewModel()..viewState = this;
29 | }
30 |
31 | @override
32 | void initAttribute() {
33 |
34 | }
35 |
36 | @override
37 | void initObserver() {}
38 |
39 | @override
40 | void dispose() {
41 | assert((){
42 | debugPrint('MessageView.onDispose()');
43 | return true;
44 | }());
45 | super.dispose();
46 | }
47 |
48 | bool executeSwitchLogin = false;
49 |
50 | @override
51 | void didChangeDependencies() {
52 | var operate = GlobalOperateProvider.getGlobalOperate(context: context);
53 |
54 | assert((){
55 | debugPrint('MessageView.didChangeDependencies --- $operate');
56 | return true;
57 | }());
58 |
59 | // 切换用户
60 | // 正常业务流程是:从本地存储,拿到当前最新的用户ID,请求接口,我这里偷了个懒 😄
61 | // 直接使用随机数,模拟 不同用户ID
62 | if (operate == GlobalOperate.switchLogin) {
63 | executeSwitchLogin = true;
64 |
65 | // 重新请求数据
66 | // 如果你想刷新的时候,显示loading,加上这两行
67 | viewModel?.pageDataModel?.type = NotifierResultType.loading;
68 | viewModel?.pageDataModel?.refreshState();
69 |
70 | viewModel?.pagingDataModel?.listData.clear();
71 | viewModel?.requestData(params: {'curPage': Random().nextInt(20)});
72 | }
73 | }
74 |
75 | @override
76 | Widget appBuild(BuildContext context) {
77 | return Scaffold(
78 | appBar: AppBar(
79 | backgroundColor: AppBarTheme.of(context).backgroundColor,
80 | title: Text(
81 | StrMessage.message,
82 | style: TextStyles.style_222222_20,
83 | )),
84 | body: NotifierPageWidget(
85 | model: viewModel?.pageDataModel,
86 | builder: (context, dataModel) {
87 | final dataList = dataModel.pagingDataModel?.listData;
88 | return Stack(
89 | children: [
90 | RefreshLoadWidget(
91 | pagingDataModel: dataModel.pagingDataModel!,
92 | scrollView: ListView.builder(
93 | padding: EdgeInsets.zero,
94 | itemCount: dataList?.length ?? 0,
95 | itemBuilder: (context, index) {
96 | var data = dataList?[index] as Datas;
97 | return Container(
98 | decoration: BoxDecoration(
99 | border: Border(
100 | bottom: BorderSide(
101 | width: 0.5,
102 | color: ColorStyles.color_000000
103 | )
104 | )
105 | ),
106 | width: MediaQuery.of(context).size.width,
107 | height: 50,
108 | alignment: Alignment.center,
109 | child: Text('${data.title}'),
110 | );
111 | }),
112 | ),
113 | Container(
114 | color: ColorStyles.color_388E3C,
115 | child: executeSwitchLogin ? Row(
116 | mainAxisAlignment: MainAxisAlignment.center,
117 | children: [
118 | Text(StrCommon.executeSwitchUser),
119 | IconButton(onPressed: (){
120 | executeSwitchLogin = false;
121 | setState(() {});
122 | }, icon: Icon(Icons.close))
123 | ],
124 | ) : SizedBox(),
125 | ),
126 | ],
127 | );
128 | },
129 | ),
130 | );
131 | }
132 |
133 | @override
134 | bool get wantKeepAlive => true;
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/lib/module/message/view_model/message_vm.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_develop_template/module/message/api/message_repository.dart';
4 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
5 | import 'package:flutter_develop_template/common/paging/paging_data_model.dart';
6 | import 'package:flutter_develop_template/module/message/model/message_list_m.dart';
7 |
8 | import '../../../common/widget/notifier_widget.dart';
9 | import '../view/message_v.dart';
10 |
11 | class MessageViewModel extends PageViewModel {
12 |
13 | CancelToken? cancelToken;
14 | PagingDataModel? pagingDataModel;
15 |
16 | @override
17 | onCreate() {
18 |
19 | assert((){
20 | /// 拿到 页面状态里的 对象、属性 等等
21 | debugPrint('---executeSwitchLogin:${state.executeSwitchLogin}');
22 | return true;
23 | }());
24 |
25 | cancelToken = CancelToken();
26 | pageDataModel = PageDataModel();
27 | pagingDataModel = PagingDataModel();
28 | requestData();
29 | }
30 |
31 | @override
32 | onDispose() {
33 | if(!(cancelToken?.isCancelled ?? true)) {
34 | cancelToken?.cancel();
35 | }
36 |
37 | assert((){
38 | debugPrint('MessageViewModel.onDispose()');
39 | return true;
40 | }());
41 |
42 | /// 别忘了执行父类的 onDispose
43 | super.onDispose();
44 | }
45 |
46 | /// 记录是否是第一次请求数据
47 | bool initRequest = true;
48 |
49 | @override
50 | Future requestData({Map? params}) async {
51 | PageViewModel viewModel = await MessageRepository().getMessageData(
52 | pageViewModel: this,
53 | cancelToken: cancelToken,
54 | curPage: params?['curPage'] ?? 1,
55 | );
56 |
57 | /// 第一次请求数据,不会触发 下拉刷新 和 上拉加载更多方法,通过标识,初始化一些Paging参数
58 | if(initRequest) {
59 | pagingDataModel?.originalListDataLength = (viewModel.pageDataModel?.data as MessageListModel).datas?.length ?? 0;
60 | }
61 |
62 | initRequest = false;
63 |
64 | pageDataModel = viewModel.pageDataModel;
65 |
66 | /// 分页代码
67 | pageDataModel?.bindingPaging(
68 | viewModel,
69 | pageDataModel!,
70 | pagingDataModel!,
71 | this);
72 |
73 | pageDataModel?.refreshState();
74 |
75 | /// 注意:使用需要返回 PageDataModel对象,它和
76 | return Future.value(viewModel);
77 | }
78 |
79 | }
80 |
81 |
--------------------------------------------------------------------------------
/lib/module/order/view_model/order_vm.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
3 |
4 | import '../view/order_v.dart';
5 |
6 | class OrderViewModel extends PageViewModel {
7 |
8 | @override
9 | onCreate() {
10 |
11 | assert((){
12 | /// 拿到 页面状态里的 对象、属性 等等
13 | debugPrint('state: --- ${state.paramsModel}');
14 | return true;
15 | }());
16 |
17 | }
18 |
19 | @override
20 | Future requestData({Map? params}) {
21 | return Future.value(null);
22 | }
23 | }
--------------------------------------------------------------------------------
/lib/module/personal/api/personal_repository.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:flutter_develop_template/common/repository/base_repository.dart';
3 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
4 | import 'package:flutter_develop_template/common/net/dio_client.dart';
5 | import 'package:flutter_develop_template/module/personal/model/user_info_m.dart';
6 |
7 | class PersonalRepository extends BaseRepository {
8 |
9 | /// 注册
10 | Future registerUser({
11 | required PageViewModel pageViewModel,
12 | Map? params,
13 | CancelToken? cancelToken,
14 | }) async =>
15 | httpPageRequest(
16 | pageViewModel: pageViewModel,
17 | cancelToken: cancelToken,
18 | jsonCoverEntity: UserInfoModel.fromJson,
19 | future: () => DioClient().doPost(
20 | 'user/register',
21 | params: params,
22 | cancelToken: cancelToken,
23 | ));
24 |
25 | /// 登陆
26 | Future loginUser({
27 | required PageViewModel pageViewModel,
28 | Map? params,
29 | CancelToken? cancelToken,
30 | }) async =>
31 | httpPageRequest(
32 | pageViewModel: pageViewModel,
33 | cancelToken: cancelToken,
34 | jsonCoverEntity: UserInfoModel.fromJson,
35 | future: () => DioClient().doPost(
36 | 'user/login',
37 | params: params,
38 | cancelToken: cancelToken,
39 | ));
40 |
41 |
42 | /// 这是不使用 httpPageRequest 的原始写法,如果业务复杂,可能还是需要在原始写法上,扩展
43 | // /// 注册
44 | // Future registerUser({
45 | // required PageViewModel pageViewModel,
46 | // Map? params,
47 | // CancelToken? cancelToken,
48 | // }) async {
49 | //
50 | // try {
51 | // Response response = await DioClient().doPost(
52 | // 'user/register',
53 | // params: params,
54 | // cancelToken: cancelToken,
55 | // );
56 | //
57 | // if(response.statusCode == REQUEST_SUCCESS) {
58 | // /// 请求成功
59 | // pageViewModel.pageDataModel?.type = NotifierResultType.success; // 请求成功
60 | //
61 | // /// ViewModel 和 Model 相互持有
62 | // UserInfoModel model = UserInfoModel.fromJson(response.data);
63 | // model.vm = pageViewModel;
64 | // pageViewModel.pageDataModel?.data = model;
65 | // } else {
66 | //
67 | // /// 请求成功,但业务不通过,比如没有权限
68 | // pageViewModel.pageDataModel?.type = NotifierResultType.unauthorized;
69 | // pageViewModel.pageDataModel?.errorMsg = response.statusMessage;
70 | // }
71 | //
72 | // } on DioException catch (dioEx) {
73 | // /// 请求异常
74 | // pageViewModel.pageDataModel?.type = NotifierResultType.dioError;
75 | // pageViewModel.pageDataModel?.errorMsg = dioErrorConversionText(dioEx);
76 | //
77 | // } catch (e) {
78 | // /// 未知异常
79 | // pageViewModel.pageDataModel?.type = NotifierResultType.fail;
80 | // pageViewModel.pageDataModel?.errorMsg = e.toString();
81 | // }
82 | //
83 | // return pageViewModel;
84 | // }
85 | //
86 | // /// 登陆
87 | // Future loginUser({
88 | // required PageViewModel pageViewModel,
89 | // Map? params,
90 | // CancelToken? cancelToken,
91 | // }) async {
92 | //
93 | // try {
94 | // Response response = await DioClient().doPost(
95 | // 'user/login',
96 | // params: params,
97 | // cancelToken: cancelToken,
98 | // );
99 | //
100 | // if(response.statusCode == REQUEST_SUCCESS) {
101 | // /// 请求成功
102 | // pageViewModel.pageDataModel?.type = NotifierResultType.success;
103 | //
104 | // /// ViewModel 和 Model 相互持有
105 | // UserInfoModel model = UserInfoModel.fromJson(response.data);
106 | // model.vm = pageViewModel;
107 | // pageViewModel.pageDataModel?.data = model;
108 | // } else {
109 | //
110 | // /// 请求成功,但业务不通过,比如没有权限
111 | // pageViewModel.pageDataModel?.type = NotifierResultType.unauthorized;
112 | // pageViewModel.pageDataModel?.errorMsg = response.statusMessage;
113 | // }
114 | //
115 | // } on DioException catch (dioEx) {
116 | // /// 请求异常
117 | // pageViewModel.pageDataModel?.type = NotifierResultType.dioError;
118 | // pageViewModel.pageDataModel?.errorMsg = dioErrorConversionText(dioEx);
119 | //
120 | // } catch (e) {
121 | // /// 未知异常
122 | // pageViewModel.pageDataModel?.type = NotifierResultType.fail;
123 | // pageViewModel.pageDataModel?.errorMsg = e.toString();
124 | // }
125 | //
126 | // return pageViewModel;
127 | // }
128 | }
129 |
--------------------------------------------------------------------------------
/lib/module/personal/model/user_info_m.dart:
--------------------------------------------------------------------------------
1 | import '../../../common/mvvm/base_model.dart';
2 |
3 | class UserInfoModel extends BaseModel {
4 | bool? admin;
5 | int? coinCount;
6 | List? collectIds;
7 | String? email;
8 | String? icon;
9 | int? id;
10 | String? nickname;
11 | String? password;
12 | String? publicName;
13 | String? token;
14 | int? type;
15 | String? username;
16 |
17 | bool isLogin = false;
18 |
19 | UserInfoModel(
20 | {this.admin,
21 | this.coinCount,
22 | this.collectIds,
23 | this.email,
24 | this.icon,
25 | this.id,
26 | this.nickname,
27 | this.password,
28 | this.publicName,
29 | this.token,
30 | this.type,
31 | this.username});
32 |
33 | UserInfoModel.fromJson(Map json) {
34 | admin = json['admin'];
35 | coinCount = json['coinCount'];
36 | collectIds = json['collectIds'].cast();
37 | email = json['email'];
38 | icon = json['icon'];
39 | id = json['id'];
40 | nickname = json['nickname'];
41 | password = json['password'];
42 | publicName = json['publicName'];
43 | token = json['token'];
44 | type = json['type'];
45 | username = json['username'];
46 | }
47 |
48 | Map toJson() {
49 | final Map data = new Map();
50 | data['admin'] = this.admin;
51 | data['coinCount'] = this.coinCount;
52 | data['collectIds'] = this.collectIds;
53 | data['email'] = this.email;
54 | data['icon'] = this.icon;
55 | data['id'] = this.id;
56 | data['nickname'] = this.nickname;
57 | data['password'] = this.password;
58 | data['publicName'] = this.publicName;
59 | data['token'] = this.token;
60 | data['type'] = this.type;
61 | data['username'] = this.username;
62 | return data;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/lib/module/personal/view/personal_v.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/services.dart';
5 | import 'package:flutter_develop_template/common/mvvm/base_page.dart';
6 | import '../../../../res/string/str_personal.dart';
7 | import 'package:flutter_develop_template/common/widget/global_notification_widget.dart';
8 | import 'package:flutter_develop_template/common/widget/notifier_widget.dart';
9 | import 'package:flutter_develop_template/main/app.dart';
10 | import 'package:flutter_develop_template/module/personal/model/user_info_m.dart';
11 |
12 | import '../../../../res/style/color_styles.dart';
13 | import '../../../../res/style/text_styles.dart';
14 | import '../../../common/util/global.dart';
15 | import '../view_model/personal_vm.dart';
16 |
17 | class PersonalView extends BaseStatefulPage {
18 | PersonalView({super.key});
19 |
20 | @override
21 | PersonalViewState createState() => PersonalViewState();
22 | }
23 |
24 | class PersonalViewState extends BaseStatefulPageState {
25 | @override
26 | void initAttribute() {}
27 |
28 | @override
29 | void initObserver() {}
30 |
31 | @override
32 | viewBindingViewModel() {
33 | /// ViewModel 和 View 相互持有
34 | return PersonalViewModel()..viewState = this;
35 | }
36 |
37 | bool executeSwitchLogin = false;
38 |
39 | @override
40 | void didChangeDependencies() {
41 | var operate = GlobalOperateProvider.getGlobalOperate(context: context);
42 |
43 | assert((){
44 | debugPrint('OrderView.didChangeDependencies --- $operate');
45 | return true;
46 | }());
47 |
48 | if (operate == GlobalOperate.switchLogin) {
49 | executeSwitchLogin = true;
50 | // 重新请求数据
51 | // viewModel.requestData();
52 | }
53 | }
54 |
55 | @override
56 | Widget appBuild(BuildContext context) {
57 | return AnnotatedRegion(
58 | value: overlayBlackStyle,
59 | child: Material(
60 | child: Stack(
61 | children: [
62 | Align(
63 | alignment: Alignment.center,
64 | child: Container(
65 | margin: EdgeInsets.only(top: kToolbarHeight + media!.padding.top),
66 | alignment: Alignment.center,
67 | child: ElevatedButton(
68 | child: Text(StrPersonal.register),
69 | onPressed: () {
70 | // 如果你想刷新的时候,显示loading,加上这两行
71 | viewModel?.pageDataModel?.type = NotifierResultType.loading;
72 | viewModel?.pageDataModel?.refreshState();
73 |
74 | var str1 = Random().nextInt(9);
75 | var str2 = Random().nextInt(9);
76 | var str3 = Random().nextInt(9);
77 | var str4 = Random().nextInt(9);
78 | var str5 = Random().nextInt(9);
79 | viewModel?.registerUser(params: {
80 | 'username': '${str1}${str2}${str3}${str4}${str5}',
81 | 'password': '123456',
82 | 'repassword': '123456'
83 | });
84 | },
85 | ),
86 | ),
87 | ),
88 | Align(
89 | alignment: Alignment.center,
90 | child: Container(
91 | margin: EdgeInsets.only(top: kToolbarHeight + media!.padding.top + 100),
92 | alignment: Alignment.center,
93 | child: ElevatedButton(
94 | child: Text(StrPersonal.login),
95 | onPressed: () {
96 | // 如果你想刷新的时候,显示loading,加上这两行
97 | viewModel?.pageDataModel?.type = NotifierResultType.loading;
98 | viewModel?.pageDataModel?.refreshState();
99 |
100 | viewModel?.loginUser(params: {
101 | 'username': 'aaaaaa',
102 | 'password': '123456',
103 | });
104 | },
105 | ),
106 | ),
107 | ),
108 | Align(
109 | alignment: Alignment.center,
110 | child: Container(
111 | margin: EdgeInsets.only(top: kToolbarHeight + media!.padding.top + 200),
112 | alignment: Alignment.center,
113 | child: ElevatedButton(
114 | child: Text(StrPersonal.switchUser),
115 | onPressed: () {
116 | // 更新本地存储的用户ID,(常用的本地存储库:shared_preferences)
117 | // ... ...
118 |
119 | // 通知所有继承 BaseStatefulPageState 的子页面
120 | GlobalOperateProvider.runGlobalOperate(context: context, operate: GlobalOperate.switchLogin);
121 | },
122 | ),
123 | ),
124 | ),
125 | Container(
126 | margin: EdgeInsets.only(top: kToolbarHeight + media!.padding.top),
127 | color: ColorStyles.color_388E3C,
128 | child: executeSwitchLogin
129 | ? Row(
130 | mainAxisAlignment: MainAxisAlignment.center,
131 | children: [
132 | Text(StrPersonal.switchUser),
133 | IconButton(
134 | onPressed: () {
135 | executeSwitchLogin = false;
136 | setState(() {});
137 | },
138 | icon: Icon(Icons.close))
139 | ],
140 | )
141 | : SizedBox(),
142 | ),
143 | _myAppBar(),
144 | ],
145 | ),
146 | ),
147 | );
148 | }
149 |
150 | _myAppBar() {
151 | return Container(
152 | width: media!.size.width,
153 | height: kToolbarHeight + media!.padding.top,
154 | padding: EdgeInsets.only(top: media!.padding.top,left: 16),
155 | color: AppBarTheme.of(context).backgroundColor,
156 | alignment: Alignment.centerLeft,
157 | child: Builder(
158 | builder: (context) {
159 | /// 初始化状态设置为 不检查,不然会 返回 loading 组件
160 | viewModel?.pageDataModel?.type = NotifierResultType.notCheck;
161 | return NotifierPageWidget(
162 | model: viewModel?.pageDataModel,
163 | builder: (context, dataModel) {
164 | final data = dataModel.data as UserInfoModel?;
165 | String title = (data?.isLogin ?? false) ? '${StrPersonal.loginSuccess}:${data?.username} ${StrPersonal.welcome}' : '${StrPersonal.registerSuccess}:${data?.username} ${StrPersonal.welcome}';
166 | return Text(
167 | (data?.username?.isEmpty ?? true) ? StrPersonal.personal : title,
168 | style: TextStyles.style_222222_20,
169 | );
170 | }
171 | );
172 | }
173 | ),
174 | );
175 | }
176 |
177 | @override
178 | bool get wantKeepAlive => true;
179 |
180 | }
181 |
--------------------------------------------------------------------------------
/lib/module/personal/view_model/personal_vm.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter_develop_template/module/personal/api/personal_repository.dart';
4 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
5 |
6 | import '../../../common/widget/notifier_widget.dart';
7 | import '../model/user_info_m.dart';
8 | import '../view/personal_v.dart';
9 |
10 | class PersonalViewModel extends PageViewModel {
11 | CancelToken? cancelToken;
12 |
13 | @override
14 | onCreate() {
15 |
16 | assert((){
17 | /// 拿到 页面状态里的 对象、属性 等等
18 | debugPrint('---executeSwitchLogin:${state.executeSwitchLogin}');
19 | return true;
20 | }());
21 |
22 | cancelToken = CancelToken();
23 | pageDataModel = PageDataModel();
24 | }
25 |
26 | @override
27 | onDispose() {
28 | if (!(cancelToken?.isCancelled ?? true)) {
29 | cancelToken?.cancel();
30 | }
31 | assert((){
32 | debugPrint('PersonalViewModel.onDispose()');
33 | return true;
34 | }());
35 |
36 | /// 别忘了执行父类的 onDispose
37 | super.onDispose();
38 | }
39 |
40 | /// 注册
41 | Future registerUser({Map? params}) async {
42 | PageViewModel viewModel = await PersonalRepository().registerUser(
43 | pageViewModel: this,
44 | cancelToken: cancelToken,
45 | params: params
46 | );
47 |
48 | (viewModel.pageDataModel?.data as UserInfoModel?)?.isLogin = false;
49 | pageDataModel = viewModel.pageDataModel;
50 | pageDataModel?.refreshState();
51 | return Future.value(viewModel);
52 | }
53 |
54 | /// 登陆
55 | Future loginUser({Map? params}) async {
56 | PageViewModel viewModel = await PersonalRepository().loginUser(
57 | pageViewModel: this,
58 | cancelToken: cancelToken,
59 | params: params
60 | );
61 | (viewModel.pageDataModel?.data as UserInfoModel?)?.isLogin = true;
62 | pageDataModel = viewModel.pageDataModel;
63 | pageDataModel?.refreshState();
64 | return Future.value(viewModel);
65 | }
66 |
67 | @override
68 | Future requestData({Map? params}) {
69 | return Future.value(null);
70 | }
71 |
72 | }
--------------------------------------------------------------------------------
/lib/module/test_fluro/page_a.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_develop_template/common/mvvm/base_page.dart';
3 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
4 | import '../../../../res/string/str_common.dart';
5 | import '../../../../router/navigator_util.dart';
6 | import 'package:flutter_develop_template/main/app.dart';
7 |
8 | import '../../../../router/routers.dart';
9 |
10 | class PageAView extends BaseStatefulPage {
11 | PageAView(
12 | {super.key,
13 | this.name,
14 | this.title,
15 | this.url,
16 | this.age,
17 | this.price,
18 | this.flag});
19 |
20 | final String? name;
21 | final String? title;
22 | final String? url;
23 | final int? age;
24 | final double? price;
25 | final bool? flag;
26 |
27 | @override
28 | PageAViewState createState() => PageAViewState();
29 | }
30 |
31 | class PageAViewState extends BaseStatefulPageState {
32 |
33 | @override
34 | void initAttribute() {}
35 |
36 | @override
37 | void initObserver() {}
38 |
39 | @override
40 | PageAViewModel viewBindingViewModel() {
41 | /// ViewModel 和 View 相互持有
42 | return PageAViewModel()..viewState = this;
43 | }
44 |
45 | @override
46 | Widget appBuild(BuildContext context) {
47 | return Scaffold(
48 | appBar: AppBar(
49 | title: Text(StrCommon.pageA),
50 | ),
51 | body: SizedBox(
52 | width: media!.size.width,
53 | height: media!.size.height,
54 | child: Column(
55 | mainAxisAlignment: MainAxisAlignment.center,
56 | crossAxisAlignment: CrossAxisAlignment.center,
57 | children: [
58 | Text('name:${widget.name}'),
59 | Text('title:${widget.title}'),
60 | Text('url:${widget.url}'),
61 | Text('age:${widget.age}'),
62 | Text('price:${widget.price}'),
63 | Text('flag:${widget.flag}'),
64 | SizedBox(height: 20),
65 | ElevatedButton(
66 | onPressed: () {
67 | NavigatorUtil.push(context, Routers.pageC, replace: true);
68 | },
69 | child: Text(StrCommon.toPageCDestroyCurrent),
70 | ),
71 | ElevatedButton(
72 | onPressed: () {
73 | NavigatorUtil.push(context, Routers.pageD);
74 | },
75 | child: Text(StrCommon.routeInterceptFromPageAtoPageD),
76 | ),
77 | ElevatedButton(
78 | onPressed: () {
79 | NavigatorUtil.back(context, arguments: '我是PageA页的Pop返回值');
80 | },
81 | child: Text(StrCommon.backPreviousPage),
82 | ),
83 | ],
84 | ),
85 | ),
86 | );
87 | }
88 |
89 | }
90 |
91 | class PageAViewModel extends PageViewModel {
92 |
93 | @override
94 | onCreate() {
95 |
96 | }
97 |
98 | @override
99 | Future requestData({Map? params}) {
100 | return Future.value(null);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/lib/module/test_fluro/page_a2.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_develop_template/common/mvvm/base_page.dart';
3 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
4 | import 'package:flutter_develop_template/main/app.dart';
5 |
6 | import '../../../../res/string/str_common.dart';
7 | import '../../../../router/navigator_util.dart';
8 |
9 | class PageA2View extends BaseStatefulPage {
10 | PageA2View(
11 | {super.key,
12 | this.name,
13 | this.title,
14 | this.url,
15 | this.age,
16 | this.price,
17 | this.flag});
18 |
19 | final String? name;
20 | final String? title;
21 | final String? url;
22 | final int? age;
23 | final double? price;
24 | final bool? flag;
25 |
26 | @override
27 | PageA2ViewState createState() => PageA2ViewState();
28 | }
29 |
30 | class PageA2ViewState extends BaseStatefulPageState {
31 |
32 | @override
33 | void initAttribute() {}
34 |
35 | @override
36 | void initObserver() {}
37 |
38 | @override
39 | PageA2ViewModel viewBindingViewModel() {
40 | /// ViewModel 和 View 相互持有
41 | return PageA2ViewModel()..viewState = this;
42 | }
43 |
44 | @override
45 | Widget appBuild(BuildContext context) {
46 | return Scaffold(
47 | appBar: AppBar(
48 | title: Text(StrCommon.pageA2),
49 | ),
50 | body: SizedBox(
51 | width: media!.size.width,
52 | height: media!.size.height,
53 | child: Column(
54 | mainAxisAlignment: MainAxisAlignment.center,
55 | crossAxisAlignment: CrossAxisAlignment.center,
56 | children: [
57 | Text('name:${widget.name}'),
58 | Text('title:${widget.title}'),
59 | Text('url:${widget.url}'),
60 | Text('age:${widget.age}'),
61 | Text('price:${widget.price}'),
62 | Text('flag:${widget.flag}'),
63 | SizedBox(height: 20),
64 | ElevatedButton(
65 | onPressed: () {
66 | NavigatorUtil.back(context, arguments: '我是PageA2页的Pop返回值');
67 | },
68 | child: Text(StrCommon.backPreviousPage),
69 | ),
70 | ],
71 | ),
72 | ),
73 | );
74 | }
75 |
76 | }
77 |
78 | class PageA2ViewModel extends PageViewModel {
79 |
80 | @override
81 | onCreate() {
82 |
83 | }
84 |
85 | @override
86 | Future requestData({Map? params}) {
87 | return Future.value(null);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/lib/module/test_fluro/page_b.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_develop_template/common/mvvm/base_page.dart';
3 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
4 | import 'package:flutter_develop_template/main/app.dart';
5 | import '../../../../res/string/str_common.dart';
6 | import '../../../../router/navigator_util.dart';
7 | import '../../../../router/routers.dart';
8 | import '../order/view/order_v.dart';
9 |
10 | class PageBView extends BaseStatefulPage {
11 | PageBView({super.key, this.paramsModel});
12 |
13 | final TestParamsModel? paramsModel;
14 |
15 | @override
16 | PageBViewState createState() => PageBViewState();
17 |
18 | }
19 |
20 | class PageBViewState extends BaseStatefulPageState {
21 |
22 | @override
23 | void initAttribute() {
24 |
25 | }
26 |
27 | @override
28 | void initObserver() {
29 |
30 | }
31 |
32 | @override
33 | PageBViewModel viewBindingViewModel() {
34 | /// ViewModel 和 View 相互持有
35 | return PageBViewModel()..viewState = this;
36 | }
37 |
38 | @override
39 | Widget appBuild(BuildContext context) {
40 | return Scaffold(
41 | appBar: AppBar(
42 | title: Text(StrCommon.pageB),
43 | ),
44 | body: SizedBox(
45 | width: media!.size.width,
46 | height: media!.size.height,
47 | child: Column(
48 | mainAxisAlignment: MainAxisAlignment.center,
49 | crossAxisAlignment: CrossAxisAlignment.center,
50 | children: [
51 | Text('name:${widget.paramsModel?.name}'),
52 | Text('title:${widget.paramsModel?.title}'),
53 | Text('url:${widget.paramsModel?.url}'),
54 | Text('age:${widget.paramsModel?.age}'),
55 | Text('price:${widget.paramsModel?.price}'),
56 | Text('flag:${widget.paramsModel?.flag}'),
57 | SizedBox(height: 20),
58 | ElevatedButton(
59 | onPressed: () {
60 | NavigatorUtil.push(context,Routers.pageD);
61 | },
62 | child: Text(StrCommon.toPageD),
63 | ),
64 | ElevatedButton(
65 | onPressed: () {
66 | NavigatorUtil.back(context,arguments: widget.paramsModel);
67 | },
68 | child: Text(StrCommon.backPreviousPage),
69 | ),
70 | ],
71 | ),
72 | ),
73 | );
74 | }
75 |
76 | }
77 |
78 | class PageBViewModel extends PageViewModel {
79 |
80 | @override
81 | onCreate() {
82 |
83 | }
84 |
85 | @override
86 | Future requestData({Map? params}) {
87 | return Future.value(null);
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/lib/module/test_fluro/page_c.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_develop_template/common/mvvm/base_page.dart';
3 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
4 |
5 | import '../../../../res/string/str_common.dart';
6 | import '../../../../router/navigator_util.dart';
7 | import '../../../../router/routers.dart';
8 | import '../../main/app.dart';
9 |
10 | class PageCView extends BaseStatefulPage {
11 | PageCView({super.key});
12 |
13 | @override
14 | PageCViewState createState() => PageCViewState();
15 | }
16 |
17 | class PageCViewState extends BaseStatefulPageState {
18 |
19 | @override
20 | void initAttribute() {
21 |
22 | }
23 |
24 | @override
25 | void initObserver() {
26 |
27 | }
28 |
29 | @override
30 | PageCViewModel viewBindingViewModel() {
31 | /// ViewModel 和 View 相互持有
32 | return PageCViewModel()..viewState = this;
33 | }
34 |
35 | @override
36 | Widget appBuild(BuildContext context) {
37 | return Scaffold(
38 | appBar: AppBar(
39 | title: Text(StrCommon.pageC),
40 | ),
41 | body: SizedBox(
42 | width: media!.size.width,
43 | height: media!.size.height,
44 | child: Column(
45 | mainAxisAlignment: MainAxisAlignment.center,
46 | crossAxisAlignment: CrossAxisAlignment.center,
47 | children: [
48 | ElevatedButton(
49 | onPressed: () {
50 | NavigatorUtil.push(context,Routers.pageD,replace:true);
51 | },
52 | child: Text(StrCommon.toPageD),
53 | ),
54 | ],
55 | ),
56 | ),
57 | );
58 | }
59 |
60 | }
61 |
62 | class PageCViewModel extends PageViewModel {
63 |
64 | @override
65 | onCreate() {
66 |
67 | }
68 |
69 | @override
70 | Future requestData({Map? params}) {
71 | return Future.value(null);
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/lib/module/test_fluro/page_d.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_develop_template/common/mvvm/base_page.dart';
3 | import 'package:flutter_develop_template/common/mvvm/base_view_model.dart';
4 |
5 | import '../../../../res/string/str_common.dart';
6 | import '../../../../router/navigator_util.dart';
7 | import '../../main/app.dart';
8 |
9 | class PageDView extends BaseStatefulPage {
10 | PageDView({super.key});
11 |
12 | @override
13 | PageDViewState createState() => PageDViewState();
14 | }
15 |
16 | class PageDViewState extends BaseStatefulPageState {
17 |
18 | @override
19 | void initAttribute() {
20 |
21 | }
22 |
23 | @override
24 | void initObserver() {
25 |
26 | }
27 |
28 | @override
29 | PageDViewModel viewBindingViewModel() {
30 | /// ViewModel 和 View 相互持有
31 | return PageDViewModel()..viewState = this;
32 | }
33 |
34 | @override
35 | Widget appBuild(BuildContext context) {
36 | return Scaffold(
37 | appBar: AppBar(
38 | title: Text(StrCommon.pageD),
39 | ),
40 | body: SizedBox(
41 | width: media!.size.width,
42 | height: media!.size.height,
43 | child: Column(
44 | mainAxisAlignment: MainAxisAlignment.center,
45 | crossAxisAlignment: CrossAxisAlignment.center,
46 | children: [
47 | ElevatedButton(
48 | onPressed: () {
49 | /// 这种是,先销毁所有路由,再新建
50 | // NavigatorUtil.push(
51 | // context,
52 | // Routers.root,
53 | // clearStack: true,
54 | // transition: TransitionType.none,
55 | // );
56 |
57 | /// 这种是 按照 栈 先进后出的原则,回退到根页面
58 | NavigatorUtil.back(context,toRoot: true);
59 | },
60 | child: Text(StrCommon.toHomeDestroyAll),
61 | ),
62 | ],
63 | ),
64 | ),
65 | );
66 | }
67 |
68 | }
69 |
70 | class PageDViewModel extends PageViewModel {
71 |
72 | @override
73 | onCreate() {
74 |
75 | }
76 |
77 | @override
78 | Future requestData({Map? params}) {
79 | return Future.value(null);
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/lib/res/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youxia-mrhan/flutter_develop_template/26f279bc9e0dc6e3c7e76be4b727ef241af09bd1/lib/res/.DS_Store
--------------------------------------------------------------------------------
/lib/res/string/str_common.dart:
--------------------------------------------------------------------------------
1 | /// 这里统一写全局,非动态、固定的 字符串文本
2 | /// 如果哪一天 项目突然要求 使用 国际化
3 | /// 直接将这里的内容,拷贝到 国际化插件生成的 映射文件中,再 对照中文 翻译成 指定语言
4 | ///
5 | /// 通用
6 | class StrCommon {
7 |
8 | static const String pleaseService = '请联系客服';
9 | static const String platformNotAdapted = '当前平台未适配';
10 | static const String executeSwitchUser = '执行了切换用户操作';
11 |
12 | static const String loading = '加载中...';
13 | static const String dataIsEmpty = '数据为空';
14 | static const String listDataIsEmpty = '列表数据为空';
15 | static const String businessDoesNotPass = '业务不通过';
16 | static const String dioErrorAnomaly = 'dioError异常';
17 | static const String unknownAnomaly = '未知异常';
18 |
19 | static const String pullDownToRefresh = '下拉刷新';
20 | static const String refreshing = '刷新中...';
21 | static const String loadedSuccess = '加载成功';
22 | static const String releaseToRefreshImmediately = '松开立即刷新';
23 | static const String refreshFailed = '刷新失败';
24 | static const String pullUpLoad = '上拉加载';
25 | static const String letGoLoading = '松手开始加载数据';
26 | static const String failedLoad = '加载失败';
27 | static const String noMoreData = '没有更多数据了';
28 |
29 | static const String connectionTimeout = '连接超时';
30 | static const String sendTimeout = '发送超时';
31 | static const String receiveTimeout = '接收超时';
32 | static const String accessCertificateError = '访问证书错误';
33 | static const String validationFailed = '验证失败';
34 | static const String connectionIsAbnormal = '连接异常';
35 | static const String unknownError = '未知错误';
36 |
37 | static const String parameterIsIncorrect = '参数有误';
38 | static const String illegalRequests = '非法请求';
39 | static const String serverRejectsRequest = '服务器拒绝请求';
40 | static const String accessAddressDoesNotExist = '访问地址不存在';
41 | static const String requestIsMadeWrongWay = '请求方式错误';
42 | static const String wasAnErrorInsideServer = '服务器内部出错了';
43 | static const String invalidRequest = '无效的请求';
44 | static const String serverIsBusy = '服务器繁忙';
45 | static const String unsupportedHttpProtocol = '不支持的HTTP协议';
46 |
47 |
48 | ///--------------------------- 测试路由 -------------------------------
49 | static const String pageA = 'PageA';
50 | static const String pageA2 = 'PageA2';
51 | static const String pageB = 'PageB';
52 | static const String pageC = 'PageC';
53 | static const String pageD = 'PageD';
54 |
55 | static const String toPageCDestroyCurrent = '前往PageC,并销毁当前页面';
56 | static const String routeInterceptFromPageAtoPageD = '路由拦截 从PageA前往PageD';
57 | static const String backPreviousPage = '返回上一页';
58 | static const String toPageD = '前往PageD';
59 | static const String toHomeDestroyAll = '前往首页,并销毁所有页面';
60 |
61 | }
--------------------------------------------------------------------------------
/lib/res/string/str_home.dart:
--------------------------------------------------------------------------------
1 | /// 这里统一写全局,非动态、固定的 字符串文本
2 | /// 如果哪一天 项目突然要求 使用 国际化
3 | /// 直接将这里的内容,拷贝到 国际化插件生成的 映射文件中,再 对照中文 翻译成 指定语言
4 | ///
5 | /// 首页
6 | class StrHome {
7 |
8 | static const String home = 'Home';
9 |
10 | }
--------------------------------------------------------------------------------
/lib/res/string/str_message.dart:
--------------------------------------------------------------------------------
1 | /// 这里统一写全局,非动态、固定的 字符串文本
2 | /// 如果哪一天 项目突然要求 使用 国际化
3 | /// 直接将这里的内容,拷贝到 国际化插件生成的 映射文件中,再 对照中文 翻译成 指定语言
4 | ///
5 | /// 消息
6 | class StrMessage {
7 |
8 | static const String message = 'Message';
9 |
10 | }
--------------------------------------------------------------------------------
/lib/res/string/str_order.dart:
--------------------------------------------------------------------------------
1 | /// 这里统一写全局,非动态、固定的 字符串文本
2 | /// 如果哪一天 项目突然要求 使用 国际化
3 | /// 直接将这里的内容,拷贝到 国际化插件生成的 映射文件中,再 对照中文 翻译成 指定语言
4 | ///
5 | /// 订单
6 | class StrOrder {
7 |
8 | static const String order = 'Order';
9 | static const String noObjectToPageA = '携带 非对象类型 前往PageA(拼接方式)';
10 | static const String noObjectToPageA2 = '携带 非对象类型 前往PageA2(arguments方式)';
11 | static const String objectToPageB = '携带 对象类型 前往PageB';
12 |
13 | }
--------------------------------------------------------------------------------
/lib/res/string/str_personal.dart:
--------------------------------------------------------------------------------
1 | /// 这里统一写全局,非动态、固定的 字符串文本
2 | /// 如果哪一天 项目突然要求 使用 国际化
3 | /// 直接将这里的内容,拷贝到 国际化插件生成的 映射文件中,再 对照中文 翻译成 指定语言
4 | ///
5 | /// 个人
6 | class StrPersonal {
7 |
8 | static const String personal = 'Personal';
9 | static const String register = '注册';
10 | static const String switchUser = '切换用户';
11 | static const String loginSuccess = '登陆成功';
12 | static const String registerSuccess = '注册成功';
13 | static const String welcome = '欢迎您';
14 | static const String login = '登陆';
15 |
16 | }
--------------------------------------------------------------------------------
/lib/res/style/color_styles.dart:
--------------------------------------------------------------------------------
1 | /// 错误的包,这种在Widget使用时,Widget找不到 样式实例
2 | /// import 'dart:ui';
3 |
4 | /// 导入正确的包
5 | import 'package:flutter/material.dart';
6 | /// 或者 import 'package:flutter/cupertino.dart';
7 |
8 | /// 全局颜色样式 统一放置的地方
9 | /// 根据颜色大概种类,进行归类
10 | class ColorStyles {
11 |
12 | static const Color color_transparent = Color(0x00000000);
13 |
14 | static const Color color_FFFFFF = Color(0xFFFFFFFF);
15 |
16 | static const Color color_000000 = Color(0xFF000000);
17 | static const Color color_222222 = Color(0xFF222222);
18 | static const Color color_474747 = Color(0xff474747);
19 |
20 | static const Color color_F94B30 = Color(0xFFF94B30);
21 | static const Color color_EA5034 = Color(0xFFEA5034);
22 | static const Color color_FF4D4F = Color(0xFFFF4D4F);
23 |
24 | static const Color color_3A65E6 = Color(0xFF3A65E6);
25 | static const Color color_456FEF = Color(0xFF456FEF);
26 | static const Color color_0E6ED9 = Color(0xFF0E6ED9);
27 | static const Color color_1E88E5 = Color(0xFF1E88E5);
28 |
29 | static const Color color_388E3C = Color(0xFF388E3C);
30 | static const Color color_2E7D32 = Color(0xFF2E7D32);
31 |
32 | }
--------------------------------------------------------------------------------
/lib/res/style/text_styles.dart:
--------------------------------------------------------------------------------
1 | /// 错误的包,这种在Widget使用时,Widget找不到 样式实例
2 | /// import 'dart:ui';
3 |
4 | /// 导入正确的包
5 | import 'package:flutter/material.dart';
6 | /// 或者 import 'package:flutter/cupertino.dart';
7 |
8 | import 'color_styles.dart';
9 |
10 | /// 全局字体样式 统一放置的地方
11 | /// 根据字体是否是加粗,进行归类
12 | class TextStyles {
13 | static final TextStyle style_000000_16 = TextStyle(
14 | color: ColorStyles.color_000000,
15 | fontSize: 16,
16 | );
17 |
18 | static final TextStyle style_222222_16 = TextStyle(
19 | color: ColorStyles.color_222222,
20 | fontSize: 16,
21 | );
22 |
23 | static final TextStyle style_222222_20 = TextStyle(
24 | color: ColorStyles.color_222222,
25 | fontSize: 20,
26 | );
27 |
28 | ///--------------------------- 字体加粗 -------------------------------
29 | static final TextStyle style_bold_000000_16 = TextStyle(
30 | color: ColorStyles.color_000000,
31 | fontSize: 16,
32 | fontWeight: FontWeight.w700,
33 | );
34 |
35 | static final TextStyle style_bold_222222_16 = TextStyle(
36 | color: ColorStyles.color_222222,
37 | fontSize: 16,
38 | fontWeight: FontWeight.w700,
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/lib/res/style/theme_styles.dart:
--------------------------------------------------------------------------------
1 | /// 错误的包,这种在Widget使用时,Widget找不到 样式实例
2 | /// import 'dart:ui';
3 |
4 | /// 导入正确的包
5 | import 'package:flutter/material.dart';
6 |
7 | import 'color_styles.dart';
8 | /// 或者 import 'package:flutter/cupertino.dart';
9 |
10 | /// 全局主题样式 统一放置的地方
11 | class ThemeStyles {
12 |
13 | static ThemeData defaultTheme = ThemeData(
14 | // appBar主题
15 | appBarTheme: AppBarTheme(backgroundColor: ColorStyles.color_0E6ED9),
16 | // 去除水波纹效果
17 | splashColor: ColorStyles.color_transparent,
18 | // 去除长按效果
19 | highlightColor: ColorStyles.color_transparent,
20 | useMaterial3: true,
21 | );
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/lib/router/navigator_util.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluro/fluro.dart';
2 | import 'package:flutter/cupertino.dart';
3 | import '../../../../router/routers.dart';
4 |
5 | import '../../main/app.dart';
6 |
7 | /// 路由工具类
8 | /// 如果所在类没有 context,可以使用 全局context:navigatorKey.currentContext
9 | class NavigatorUtil {
10 |
11 | /// 前往页面
12 | static Future push(
13 | BuildContext context,
14 | path, {
15 | bool replace = false, // 替换,进入新的页面时,将当前页面销毁
16 | bool clearStack = false, // 清空路由栈,进入新的页面时,其他实例的页面全部销毁
17 | Object? arguments, // 传递的参数
18 | TransitionType? transition, // 页面跳转的动画 类型
19 | }) {
20 | return Routers.router.navigateTo(
21 | context,
22 | path,
23 | replace: replace,
24 | clearStack: clearStack,
25 | transition: transition ?? TransitionType.inFromRight, // 默认跳转动画,从右侧进入
26 | routeSettings: RouteSettings(
27 | arguments: arguments,
28 | ),
29 | );
30 | }
31 |
32 | /// 返回
33 | static void back(
34 | BuildContext context, {
35 | int count = 1, // 指定返回多少层
36 | bool toRoot = false, // 直接返回根目录
37 | Object? arguments,
38 | }) {
39 | if(toRoot) {
40 | /// 获取路由栈里,页面总数量
41 | int? routeStackLength = pageRouteObserver?.getRouteStackLength() ?? 1;
42 | count = routeStackLength;
43 | if(count == 1) {
44 | /// 路由栈里,只有1个页面
45 | return;
46 | } else {
47 | /// 减1,保留 根页面
48 | count--;
49 | }
50 | }
51 | NavigatorState state = Navigator.of(context);
52 | while(count-- > 0) {
53 | state = state..pop(arguments);
54 | }
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/lib/router/page_route_observer.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import '../../../../router/routers.dart';
3 |
4 | import '../../main/app.dart';
5 | import 'navigator_util.dart';
6 |
7 | /// 监听路由栈状态
8 | class PageRouteObserver extends NavigatorObserver {
9 | static List> routeStack = [];
10 |
11 | @override
12 | void didPush(Route route, Route? previousRoute) {
13 | super.didPush(route, previousRoute);
14 |
15 | /// 当前所在页面 Path
16 | String? currentRoutePath = getOriginalPath(previousRoute);
17 |
18 | /// 要前往的页面 Path
19 | String? newRoutePath = getOriginalPath(route);
20 |
21 | /// 拦截指定页面
22 | /// 如果从 PageA 页面,跳转到 PageD,将其拦截
23 | if(currentRoutePath == Routers.pageA) {
24 |
25 | if(newRoutePath == Routers.pageD) {
26 | assert((){
27 | debugPrint('准备从 PageA页面 进入 pageD页面,进行登陆信息验证');
28 | return true;
29 | }());
30 |
31 | // if(验证不通过) {
32 | /// 注意:要延迟一帧
33 | WidgetsBinding.instance.addPostFrameCallback((_){
34 | // 我这里是pop,视觉上达到无法进入新页面的效果,
35 | // 正常业务是跳转到 登陆页面
36 | NavigatorUtil.back(navigatorKey.currentContext!);
37 | });
38 | // }
39 | }
40 |
41 | }
42 |
43 | routeStack.add(route);
44 | printRouteStack();
45 | }
46 |
47 | @override
48 | void didPop(Route route, Route? previousRoute) {
49 | super.didPop(route, previousRoute);
50 | routeStack.remove(route);
51 | printRouteStack();
52 | }
53 |
54 | @override
55 | void didRemove(Route route, Route? previousRoute) {
56 | super.didRemove(route, previousRoute);
57 | routeStack.remove(route);
58 | printRouteStack();
59 | }
60 |
61 | @override
62 | void didReplace({Route? newRoute, Route? oldRoute}) {
63 | super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
64 | if (oldRoute != null) {
65 | routeStack.remove(oldRoute);
66 | }
67 | if (newRoute != null) {
68 | routeStack.add(newRoute);
69 | }
70 | printRouteStack();
71 | }
72 |
73 | void printRouteStack() {
74 | assert((){
75 | debugPrint('当前路由栈: ${routeStack.map((route) => route.settings.name).toList()}');
76 | debugPrint('当前路由栈长度: ${routeStack.length}');
77 | return true;
78 | }());
79 | }
80 |
81 | /// 返回当前路由栈的长度
82 | int getRouteStackLength() {
83 | return routeStack.length;
84 | }
85 | }
86 |
87 | /// 获取原生路径
88 | /// 使用 path拼接方式 传递 参数,会改变原来的 路由页面 Path
89 | ///
90 | /// 比如:NavigatorUtil.push(context,'${Routers.pageA}?name=$name&title=$title&url=$url&age=$age&price=$price&flag=$flag');
91 | /// path会变成:/pageA?name=jk&title=%E5%BC%A0%E4%B8%89&url=https%3A%2F%2Fwww.baidu.com&age=99&price=9.9&flag=true
92 | /// 所以再次匹配pageA,找不到,需要还原一下,getOriginalPath(path)
93 | String? getOriginalPath(Route? route) {
94 | // 获取原始的路由路径
95 | String? fullPath = route?.settings.name;
96 |
97 | if(fullPath != null) {
98 | // 使用正则表达式去除查询参数
99 | return fullPath.split('?')[0];
100 | }
101 |
102 | return fullPath;
103 | }
--------------------------------------------------------------------------------
/lib/router/routers.dart:
--------------------------------------------------------------------------------
1 | import 'package:fluro/fluro.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_develop_template/main/app.dart';
4 | import 'package:flutter_develop_template/module/test_fluro/page_a.dart';
5 | import 'package:flutter_develop_template/module/test_fluro/page_a2.dart';
6 | import 'package:flutter_develop_template/module/test_fluro/page_b.dart';
7 | import 'package:flutter_develop_template/module/test_fluro/page_c.dart';
8 |
9 | import '../../module/order/view/order_v.dart';
10 | import '../../module/test_fluro/page_d.dart';
11 |
12 | class Routers {
13 | static FluroRouter router = FluroRouter();
14 |
15 | // 配置路由
16 | static void configureRouters() {
17 | router.notFoundHandler = Handler(handlerFunc: (_, __) {
18 | // 找不到路由时,返回指定提示页面
19 | return Scaffold(
20 | body: const Center(
21 | child: Text('404'),
22 | ),
23 | );
24 | });
25 |
26 | // 初始化路由
27 | _initRouter();
28 | }
29 |
30 | // 设置页面
31 |
32 | // 页面标识
33 | static String root = '/';
34 |
35 | // 页面A
36 | static String pageA = '/pageA';
37 |
38 | // 页面A2
39 | static String pageA2 = '/pageA2';
40 |
41 | // 页面B
42 | static String pageB = '/pageB';
43 |
44 | // 页面C
45 | static String pageC = '/pageC';
46 |
47 | // 页面D
48 | static String pageD = '/pageD';
49 |
50 | // 注册路由
51 | static _initRouter() {
52 |
53 | // 根页面
54 | router.define(
55 | root,
56 | handler: Handler(
57 | handlerFunc: (_, __) => AppMainPage(),
58 | ),
59 | );
60 |
61 | // 页面A 需要 非对象类型 参数(通过 拼接 传参数)
62 | router.define(
63 | pageA,
64 | handler: Handler(
65 | handlerFunc: (_, Map> params) {
66 |
67 | // 获取路由参数
68 | String? name = params['name']?.first;
69 | String? title = params['title']?.first;
70 | String? url = params['url']?.first;
71 | String? age = params['age']?.first ?? '-1';
72 | String? price = params['price']?.first ?? '-1';
73 | String? flag = params['flag']?.first ?? 'false';
74 |
75 | return PageAView(
76 | name: name,
77 | title: title,
78 | url: url,
79 | age: int.parse(age),
80 | price: double.parse(price),
81 | flag: bool.parse(flag)
82 | );
83 |
84 | },
85 | ),
86 | );
87 |
88 | // 页面A2 需要 非对象类型 参数(通过 arguments 传参数)
89 | router.define(
90 | pageA2,
91 | handler: Handler(
92 | handlerFunc: (context, _) {
93 |
94 | // 获取路由参数
95 | final arguments = context?.settings?.arguments as Map;
96 |
97 | String? name = arguments['name'] as String?;
98 | String? title = arguments['title'] as String?;
99 | String? url = arguments['url'] as String?;
100 | int? age = arguments['age'] as int?;
101 | double? price = arguments['price'] as double?;
102 | bool? flag = arguments['flag'] as bool?;
103 |
104 | return PageA2View(
105 | name: name,
106 | title: title,
107 | url: url,
108 | age: age,
109 | price: price,
110 | flag: flag ?? false
111 | );
112 |
113 | },
114 | ),
115 | );
116 |
117 | // 页面B 需要 对象类型 参数
118 | router.define(
119 | pageB,
120 | handler: Handler(
121 | handlerFunc: (context, Map> params) {
122 | // 获取路由参数
123 | TestParamsModel? paramsModel = context?.settings?.arguments as TestParamsModel?;
124 | return PageBView(paramsModel: paramsModel);
125 | },
126 | ),
127 | );
128 |
129 | // 页面C 无参数
130 | router.define(
131 | pageC,
132 | handler: Handler(
133 | handlerFunc: (_, __) => PageCView(),
134 | ),
135 | );
136 |
137 | // 页面D 无参数
138 | router.define(
139 | pageD,
140 | handler: Handler(
141 | handlerFunc: (_, __) => PageDView(),
142 | ),
143 | );
144 | }
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_develop_template
2 | description: "A new Flutter project."
3 | # The following line prevents the package from being accidentally published to
4 | # pub.dev using `flutter pub publish`. This is preferred for private packages.
5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
6 |
7 | # The following defines the version and build number for your application.
8 | # A version number is three numbers separated by dots, like 1.2.43
9 | # followed by an optional build number separated by a +.
10 | # Both the version and the builder number may be overridden in flutter
11 | # build by specifying --build-name and --build-number, respectively.
12 | # In Android, build-name is used as versionName while build-number used as versionCode.
13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
15 | # Read more about iOS versioning at
16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17 | # In Windows, build-name is used as the major, minor, and patch parts
18 | # of the product and file versions while build-number is used as the build suffix.
19 | version: 1.0.0+1
20 |
21 | environment:
22 | sdk: '>=3.4.1 <4.0.0'
23 |
24 | # Dependencies specify other packages that your package needs in order to work.
25 | # To automatically upgrade your package dependencies to the latest versions
26 | # consider running `flutter pub upgrade --major-versions`. Alternatively,
27 | # dependencies can be manually updated by changing the version numbers below to
28 | # the latest version available on pub.dev. To see which dependencies have newer
29 | # versions available, run `flutter pub outdated`.
30 | dependencies:
31 | flutter:
32 | sdk: flutter
33 |
34 |
35 | # The following adds the Cupertino Icons font to your application.
36 | # Use with the CupertinoIcons class for iOS style icons.
37 | cupertino_icons: ^1.0.6
38 | dio: ^5.4.3+1
39 | fluro: ^2.0.5
40 | pull_to_refresh: ^2.0.0
41 | sentry_flutter: ^7.8.0
42 |
43 | dev_dependencies:
44 | flutter_test:
45 | sdk: flutter
46 |
47 | # The "flutter_lints" package below contains a set of recommended lints to
48 | # encourage good coding practices. The lint set provided by the package is
49 | # activated in the `analysis_options.yaml` file located at the root of your
50 | # package. See that file for information about deactivating specific lint
51 | # rules and activating additional ones.
52 | flutter_lints: ^3.0.0
53 |
54 | # For information on the generic Dart part of this file, see the
55 | # following page: https://dart.dev/tools/pub/pubspec
56 |
57 | # The following section is specific to Flutter packages.
58 | flutter:
59 |
60 | # The following line ensures that the Material Icons font is
61 | # included with your application, so that you can use the icons in
62 | # the material Icons class.
63 | uses-material-design: true
64 |
65 | # To add assets to your application, add an assets section, like this:
66 | # assets:
67 | # - images/a_dot_burr.jpeg
68 | # - images/a_dot_ham.jpeg
69 |
70 | # An image asset can refer to one or more resolution-specific "variants", see
71 | # https://flutter.dev/assets-and-images/#resolution-aware
72 |
73 | # For details regarding adding assets from package dependencies, see
74 | # https://flutter.dev/assets-and-images/#from-packages
75 |
76 | # To add custom fonts to your application, add a fonts section here,
77 | # in this "flutter" section. Each entry in this list should have a
78 | # "family" key with the font family name, and a "fonts" key with a
79 | # list giving the asset and other descriptors for the font. For
80 | # example:
81 | # fonts:
82 | # - family: Schyler
83 | # fonts:
84 | # - asset: fonts/Schyler-Regular.ttf
85 | # - asset: fonts/Schyler-Italic.ttf
86 | # style: italic
87 | # - family: Trajan Pro
88 | # fonts:
89 | # - asset: fonts/TrajanPro.ttf
90 | # - asset: fonts/TrajanPro_Bold.ttf
91 | # weight: 700
92 | #
93 | # For details regarding fonts from package dependencies,
94 | # see https://flutter.dev/custom-fonts/#from-packages
95 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_develop_template/main/app.dart';
10 | import 'package:flutter_test/flutter_test.dart';
11 |
12 | void main() {
13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
14 | // Build our app and trigger a frame.
15 | await tester.pumpWidget(const App());
16 |
17 | // Verify that our counter starts at 0.
18 | expect(find.text('0'), findsOneWidget);
19 | expect(find.text('1'), findsNothing);
20 |
21 | // Tap the '+' icon and trigger a frame.
22 | await tester.tap(find.byIcon(Icons.add));
23 | await tester.pump();
24 |
25 | // Verify that our counter has incremented.
26 | expect(find.text('0'), findsNothing);
27 | expect(find.text('1'), findsOneWidget);
28 | });
29 | }
30 |
--------------------------------------------------------------------------------