flu-cli-core 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +9 -0
  2. package/dist/chunk-BGYZU6TU.js +466 -0
  3. package/dist/chunk-QGM4M3NI.js +37 -0
  4. package/dist/factory-LM2CTHPW.js +7 -0
  5. package/dist/factory-P6ABQFH3.js +7 -0
  6. package/dist/index.cjs +17765 -3244
  7. package/dist/index.d.cts +783 -99
  8. package/dist/index.d.ts +783 -99
  9. package/dist/index.js +17322 -2942
  10. package/dist/upgrade_snippets-BJ6CQY5Q.js +9 -0
  11. package/package.json +3 -3
  12. package/templates/README.md +12 -0
  13. package/templates/core_files/auth/auth_middleware.dart.template +33 -0
  14. package/templates/core_files/auth/auth_service.dart.template +22 -0
  15. package/templates/core_files/auth/auth_viewmodel_mixin.dart.template +9 -0
  16. package/templates/core_files/auth/index.dart.template +4 -0
  17. package/templates/core_files/base/base_service.dart.template +12 -0
  18. package/templates/core_files/base/index.dart.template +3 -0
  19. package/templates/core_files/config/agreement_document_page.dart.template +220 -0
  20. package/templates/core_files/config/app_agreement.dart.template +297 -0
  21. package/templates/core_files/config/app_config.dart.template +81 -22
  22. package/templates/core_files/config/app_env.dart.template +107 -0
  23. package/templates/core_files/config/app_initializer.dart.template +16 -23
  24. package/templates/core_files/config/index.dart.template +4 -1
  25. package/templates/core_files/config/privacy_dialog.dart.template +158 -0
  26. package/templates/core_files/index.dart.template +3 -0
  27. package/templates/core_files/mixins/page/keep_alive_mixin.dart.template +6 -0
  28. package/templates/core_files/mixins/page/scroll_controller_mixin.dart.template +7 -0
  29. package/templates/core_files/mixins/service/request_guard_mixin.dart.template +18 -0
  30. package/templates/core_files/mixins/viewmodel/debounce_mixin.dart.template +18 -0
  31. package/templates/core_files/network/app_http.dart.template +19 -4
  32. package/templates/core_files/network/index.dart.template +4 -0
  33. package/templates/core_files/network/interceptors/global_params_interceptor.dart.template +77 -0
  34. package/templates/core_files/network/interceptors/index.dart.template +3 -0
  35. package/templates/core_files/network/network_monitor.dart.template +18 -0
  36. package/templates/core_files/network/response_adapter.dart.template +8 -19
  37. package/templates/core_files/router/app_routes.dart.template +3 -6
  38. package/templates/core_files/storage/storage_keys.dart.template +6 -0
  39. package/templates/core_files/theme/app_color_config.dart.template +32 -0
  40. package/templates/core_files/theme/app_text_size_config.dart.template +22 -0
  41. package/templates/core_files/theme/app_text_style_config.dart.template +139 -0
  42. package/templates/core_files/theme/app_theme.dart.template +72 -12
  43. package/templates/core_files/theme/index.dart.template +3 -0
  44. package/templates/core_files/utils/loading_util.dart.template +1 -1
  45. package/templates/examples/eg_list_page.dart.template +1 -2
  46. package/templates/examples/eg_service.dart.template +27 -4
  47. package/templates/examples/home_feed_service.dart.template +37 -0
  48. package/templates/helper_examples/image_picker_example_page.dart.template +289 -0
  49. package/templates/helper_examples/index.dart.template +4 -0
  50. package/templates/helper_examples/payment_shell_example_page.dart.template +67 -0
  51. package/templates/helper_examples/permission_example_page.dart.template +365 -0
  52. package/templates/helper_examples/webview_example_page.dart.template +44 -0
  53. package/templates/helpers/image_picker/README.md.template +30 -0
  54. package/templates/helpers/image_picker/index.dart.template +73 -0
  55. package/templates/helpers/payment/README.md.template +29 -0
  56. package/templates/helpers/payment/index.dart.template +66 -0
  57. package/templates/helpers/permission/README.md.template +30 -0
  58. package/templates/helpers/permission/index.dart.template +67 -0
  59. package/templates/helpers/webview/README.md.template +29 -0
  60. package/templates/helpers/webview/index.dart.template +88 -0
  61. package/templates/starter_project/.env.dev.template +14 -0
  62. package/templates/starter_project/.env.prod.example.template +14 -0
  63. package/templates/starter_project/.env.staging.template +14 -0
  64. package/templates/starter_project/.vscode/launch.json.template +54 -0
  65. package/templates/starter_project/DEVELOPER_GUIDE.md.template +150 -0
  66. package/templates/starter_project/README.md.template +99 -0
  67. package/templates/starter_project/analysis_options.yaml.template +28 -0
  68. package/templates/starter_project/lib/app.dart.template +22 -0
  69. package/templates/starter_project/lib/main.dart.template +34 -0
  70. package/templates/starter_project/lib/pages/splash_page.dart.template +154 -0
  71. package/templates/template_clean/lib/features/home/data/datasources/index.dart +1 -0
  72. package/templates/template_clean/lib/features/home/data/models/index.dart +1 -0
  73. package/templates/template_clean/lib/features/home/domain/index.dart +1 -0
  74. package/templates/template_clean/lib/features/home/presentation/pages/home_page.dart +290 -0
  75. package/templates/template_clean/lib/features/home/presentation/pages/index.dart +2 -0
  76. package/templates/template_clean/lib/features/home/presentation/pages/splash_page.dart +154 -0
  77. package/templates/template_clean/lib/features/home/presentation/viewmodels/home_viewmodel.dart +17 -0
  78. package/templates/template_clean/lib/features/index.dart +2 -0
  79. package/templates/template_clean/lib/features/user/data/datasources/home_feed_service.dart +37 -0
  80. package/templates/template_clean/lib/features/user/data/datasources/index.dart +4 -0
  81. package/templates/template_clean/lib/features/user/data/models/index.dart +3 -0
  82. package/templates/template_clean/lib/features/user/data/models/user.dart +15 -0
  83. package/templates/template_clean/lib/features/user/domain/index.dart +1 -0
  84. package/templates/template_clean/lib/features/user/presentation/pages/index.dart +1 -0
  85. package/templates/template_clean/lib/features/user/presentation/pages/user_list_page.dart +27 -0
  86. package/templates/template_clean/lib/features/user/presentation/viewmodels/user_list_viewmodel.dart +88 -0
  87. package/templates/template_clean/lib/features/user/presentation/widgets/user_item_card.dart +24 -0
  88. package/templates/template_clean/lib/shared/extensions/index.dart +1 -0
  89. package/templates/template_clean/lib/shared/widgets/index.dart +1 -0
  90. package/templates/template_lite/lib/models/index.dart +1 -0
  91. package/templates/template_lite/lib/pages/home_page.dart +290 -0
  92. package/templates/template_lite/lib/pages/index.dart +3 -0
  93. package/templates/template_lite/lib/pages/splash_page.dart +154 -0
  94. package/templates/template_lite/lib/pages/user_list_page.dart +29 -0
  95. package/templates/template_lite/lib/services/home_feed_service.dart +37 -0
  96. package/templates/template_lite/lib/services/index.dart +5 -0
  97. package/templates/template_lite/lib/utils/index.dart +1 -0
  98. package/templates/template_lite/lib/viewmodels/home_viewmodel.dart +34 -0
  99. package/templates/template_lite/lib/viewmodels/index.dart +2 -0
  100. package/templates/template_lite/lib/viewmodels/user_list_viewmodel.dart +103 -0
  101. package/templates/template_lite/lib/widgets/index.dart +1 -0
  102. package/templates/template_lite/lib/widgets/user_item_widget.dart +57 -0
  103. package/templates/template_modular/lib/features/home/index.dart +2 -0
  104. package/templates/template_modular/lib/features/home/models/index.dart +1 -0
  105. package/templates/template_modular/lib/features/home/pages/home_page.dart +290 -0
  106. package/templates/template_modular/lib/features/home/pages/index.dart +2 -0
  107. package/templates/template_modular/lib/features/home/pages/splash_page.dart +154 -0
  108. package/templates/template_modular/lib/features/home/services/index.dart +1 -0
  109. package/templates/template_modular/lib/features/home/viewmodels/home_viewmodel.dart +17 -0
  110. package/templates/template_modular/lib/features/index.dart +2 -0
  111. package/templates/template_modular/lib/features/user/index.dart +6 -0
  112. package/templates/template_modular/lib/features/user/pages/user_list_page.dart +26 -0
  113. package/templates/template_modular/lib/features/user/services/home_feed_service.dart +37 -0
  114. package/templates/template_modular/lib/features/user/viewmodels/user_list_viewmodel.dart +103 -0
  115. package/templates/template_modular/lib/features/user/widgets/user_item_widget.dart +24 -0
  116. package/templates/template_modular/lib/shared/utils/index.dart +1 -0
  117. package/templates/template_modular/lib/shared/widgets/index.dart +1 -0
@@ -2,20 +2,20 @@ import 'package:flutter/foundation.dart';
2
2
 
3
3
  import '../storage/storage_keys.dart';
4
4
  import '../storage/storage_util.dart';
5
+ import 'app_env.dart';
5
6
 
6
- /// 应用常量
7
+ /// 应用常量(不随环境变化的固定值)
7
8
  class AppConstants {
8
9
  AppConstants._();
9
10
 
10
- static const String appName = '火叶';
11
- static const String appVersion = '1.0.0';
11
+ /// 应用名称 (便捷访问)
12
+ static String get appName => EnvConfig.appName;
13
+ static String get appVersion => EnvConfig.appVersion;
12
14
 
13
- /// 免责声明
14
- static const String disclaimer =
15
- '本应用仅供学习交流使用,API 数据由第三方提供,本应用不保证数据的准确性、完整性及安全性。如有侵权,请联系删除。';
16
-
17
- /// 国内可直接访问的 API 地址 (示例使用 图虫API)
18
- static const String apiBaseUrl = 'https://api.tuchong.com';
15
+ /// API 基础地址。
16
+ ///
17
+ /// 默认值仅作为占位,真实业务项目应通过 env 文件或 dart-define 覆盖。
18
+ static String get apiBaseUrl => EnvConfig.apiBaseUrl;
19
19
 
20
20
  static const int apiTimeout = 30000;
21
21
  static const int pageSize = 20;
@@ -23,7 +23,7 @@ class AppConstants {
23
23
 
24
24
  /// 应用配置
25
25
  ///
26
- /// 统一管理应用配置和生命周期状态
26
+ /// 合并编译期环境变量 + 运行时状态,统一对外提供
27
27
  ///
28
28
  /// 使用示例:
29
29
  /// ```dart
@@ -37,23 +37,38 @@ class AppConfig {
37
37
  static AppConfig? _instance;
38
38
  static AppConfig get I => _instance!;
39
39
 
40
- // ==================== 配置 ====================
40
+ // ==================== 环境配置(来自 .env) ====================
41
+
42
+ /// 当前运行环境
43
+ final AppEnv env;
41
44
 
42
45
  /// API 基础地址
43
46
  final String apiBaseUrl;
44
47
 
48
+ /// 是否启用日志
49
+ final bool enableLog;
50
+
51
+ /// 应用名称 (便捷访问)
52
+ String get appName => EnvConfig.appName;
53
+
54
+ // ==================== 运行时配置 ====================
55
+
45
56
  /// 连接超时时间
46
57
  final Duration connectTimeout;
47
58
 
48
59
  /// 接收超时时间
49
60
  final Duration receiveTimeout;
50
61
 
51
- /// 是否启用日志
52
- final bool enableLog;
53
-
54
- /// 是否使用 Mock 数据 (用于网络示例演示)
62
+ /// 是否使用 Mock 数据。
63
+ ///
64
+ /// 只有选择示例时,示例服务才会读取该开关。
55
65
  final bool useMockData;
56
66
 
67
+ /// 已接受的协议版本。
68
+ ///
69
+ /// 空字符串表示尚未接受当前协议。
70
+ String agreementAcceptedVersion;
71
+
57
72
  // ==================== 生命周期状态 ====================
58
73
 
59
74
  /// 是否首次启动
@@ -68,7 +83,12 @@ class AppConfig {
68
83
  /// 上一个版本号
69
84
  final String previousVersion;
70
85
 
86
+ // ==================== 扩展配置 ====================
87
+
88
+ final dynamic extra;
89
+
71
90
  AppConfig._({
91
+ required this.env,
72
92
  required this.apiBaseUrl,
73
93
  required this.connectTimeout,
74
94
  required this.receiveTimeout,
@@ -78,25 +98,26 @@ class AppConfig {
78
98
  required this.isFirstLaunchAfterUpdate,
79
99
  required this.currentVersion,
80
100
  required this.previousVersion,
101
+ required this.agreementAcceptedVersion,
102
+ this.extra,
81
103
  });
82
104
 
83
105
  /// 初始化应用配置
84
106
  ///
85
107
  /// 必须在应用启动时调用(runApp 之前)
86
108
  static Future<void> init({
87
- String? apiBaseUrl,
88
109
  Duration connectTimeout =
89
110
  const Duration(milliseconds: AppConstants.apiTimeout),
90
111
  Duration receiveTimeout =
91
112
  const Duration(milliseconds: AppConstants.apiTimeout),
92
- bool? enableLog,
93
113
  bool useMockData = false,
114
+ dynamic extra,
94
115
  }) async {
95
116
  // 1. 初始化存储
96
117
  await StorageUtil.init();
97
118
 
98
119
  // 2. 获取版本信息
99
- const currentVersion = AppConstants.appVersion;
120
+ final currentVersion = AppConstants.appVersion;
100
121
  final previousVersion = StorageUtil.getString(StorageKeys.appVersion) ?? '';
101
122
 
102
123
  // 3. 判断启动状态
@@ -104,23 +125,33 @@ class AppConfig {
104
125
  final isFirstLaunchAfterUpdate =
105
126
  previousVersion.isNotEmpty && previousVersion != currentVersion;
106
127
 
107
- // 4. 更新存储
128
+ // 4. 获取协议状态。兼容早期只存 bool 的 demo 版本。
129
+ final legacyPrivacyAgreed =
130
+ StorageUtil.getBool(StorageKeys.privacyAgreed) ?? false;
131
+ final agreementAcceptedVersion =
132
+ StorageUtil.getString(StorageKeys.agreementAcceptedVersion) ??
133
+ (legacyPrivacyAgreed ? EnvConfig.agreementVersion : '');
134
+
135
+ // 5. 更新存储
108
136
  if (isFirstLaunch) {
109
137
  await StorageUtil.setBool(StorageKeys.isFirstLaunch, false);
110
138
  }
111
139
  await StorageUtil.setString(StorageKeys.appVersion, currentVersion);
112
140
 
113
- // 5. 创建实例
141
+ // 6. 创建实例
114
142
  _instance = AppConfig._(
115
- apiBaseUrl: apiBaseUrl ?? AppConstants.apiBaseUrl,
143
+ env: AppEnv.fromString(EnvConfig.env),
144
+ apiBaseUrl: EnvConfig.apiBaseUrl,
116
145
  connectTimeout: connectTimeout,
117
146
  receiveTimeout: receiveTimeout,
118
- enableLog: enableLog ?? kDebugMode,
147
+ enableLog: EnvConfig.enableLog,
119
148
  useMockData: useMockData,
120
149
  isFirstLaunch: isFirstLaunch,
121
150
  isFirstLaunchAfterUpdate: isFirstLaunchAfterUpdate,
122
151
  currentVersion: currentVersion,
123
152
  previousVersion: previousVersion,
153
+ agreementAcceptedVersion: agreementAcceptedVersion,
154
+ extra: extra,
124
155
  );
125
156
 
126
157
  _printInitLog();
@@ -130,13 +161,41 @@ class AppConfig {
130
161
  if (!_instance!.enableLog) return;
131
162
  debugPrint('┌─────────────────────────────────────');
132
163
  debugPrint('│ AppConfig 初始化完成');
164
+ debugPrint('│ 环境: ${_instance!.env.name}');
133
165
  debugPrint('│ API: ${_instance!.apiBaseUrl}');
166
+ debugPrint('│ 应用: ${_instance!.appName}');
134
167
  debugPrint('│ 版本: ${_instance!.currentVersion}');
135
168
  debugPrint('│ 首次启动: ${_instance!.isFirstLaunch}');
136
169
  debugPrint('│ 更新后首次: ${_instance!.isFirstLaunchAfterUpdate}');
170
+ debugPrint('│ 协议版本: ${EnvConfig.agreementVersion}');
171
+ debugPrint('│ 协议已同意: ${_instance!.hasAcceptedAgreement}');
172
+ if (_instance!.extra != null) {
173
+ debugPrint('│ 扩展配置: ${_instance!.extra}');
174
+ }
137
175
  debugPrint('└─────────────────────────────────────');
138
176
  }
139
177
 
178
+ /// 便捷判断
179
+ bool get isDev => env == AppEnv.dev;
180
+ bool get isStaging => env == AppEnv.staging;
181
+ bool get isProduction => env == AppEnv.production;
182
+
183
+ bool get hasAcceptedAgreement =>
184
+ agreementAcceptedVersion == EnvConfig.agreementVersion;
185
+
186
+ bool get shouldShowSplash =>
187
+ EnvConfig.enableAgreementDialog && !hasAcceptedAgreement;
188
+
189
+ /// 接受当前协议版本。
190
+ static Future<void> acceptCurrentAgreement() async {
191
+ _instance?.agreementAcceptedVersion = EnvConfig.agreementVersion;
192
+ await StorageUtil.setString(
193
+ StorageKeys.agreementAcceptedVersion,
194
+ EnvConfig.agreementVersion,
195
+ );
196
+ await StorageUtil.setBool(StorageKeys.privacyAgreed, true);
197
+ }
198
+
140
199
  /// 是否已初始化
141
200
  static bool get isInitialized => _instance != null;
142
201
  }
@@ -0,0 +1,107 @@
1
+ // 编译期环境变量读取。
2
+ // 所有值都可通过 `--dart-define-from-file` 或 `--dart-define` 覆盖。
3
+
4
+ /// 运行环境
5
+ enum AppEnv {
6
+ dev,
7
+ staging,
8
+ production;
9
+
10
+ /// 从 .env 文件的 APP_ENV 字符串解析
11
+ static AppEnv fromString(String value) {
12
+ return AppEnv.values.firstWhere(
13
+ (e) => e.name == value,
14
+ orElse: () => AppEnv.dev,
15
+ );
16
+ }
17
+ }
18
+
19
+ class EnvConfig {
20
+ EnvConfig._();
21
+
22
+ /// 当前运行环境
23
+ static const String env = String.fromEnvironment(
24
+ 'APP_ENV',
25
+ defaultValue: 'dev',
26
+ );
27
+
28
+ /// 应用名称
29
+ static const String appName = String.fromEnvironment(
30
+ 'APP_NAME',
31
+ defaultValue: '{{projectDisplayName}}',
32
+ );
33
+
34
+ /// 应用版本
35
+ static const String appVersion = String.fromEnvironment(
36
+ 'APP_VERSION',
37
+ defaultValue: '1.0.0',
38
+ );
39
+
40
+ /// 应用运营商名称
41
+ static const String appOperatorName = String.fromEnvironment(
42
+ 'APP_OPERATOR_NAME',
43
+ defaultValue: '{{projectDisplayName}} Team',
44
+ );
45
+
46
+ /// 隐私联系邮箱
47
+ static const String privacyContactEmail = String.fromEnvironment(
48
+ 'PRIVACY_CONTACT_EMAIL',
49
+ defaultValue: 'privacy@example.com',
50
+ );
51
+
52
+ /// 隐私协议生效日期
53
+ static const String agreementEffectiveDate = String.fromEnvironment(
54
+ 'AGREEMENT_EFFECTIVE_DATE',
55
+ defaultValue: '2026-01-01',
56
+ );
57
+
58
+ /// API 基础地址
59
+ static const String apiBaseUrl = String.fromEnvironment(
60
+ 'API_BASE_URL',
61
+ defaultValue: 'https://example.com',
62
+ );
63
+
64
+ /// 是否启用日志
65
+ static const bool enableLog = bool.fromEnvironment(
66
+ 'ENABLE_LOG',
67
+ defaultValue: true,
68
+ );
69
+
70
+ /// 是否使用 Mock 数据
71
+ static const bool useMockData = bool.fromEnvironment(
72
+ 'USE_MOCK_DATA',
73
+ defaultValue: true,
74
+ );
75
+
76
+ /// 首页样板列表模式。
77
+ ///
78
+ /// 仅用于 starter 验收样板;真实网络示例接入时应替换同一首页列表槽位。
79
+ static const String demoListMode = String.fromEnvironment(
80
+ 'DEMO_LIST_MODE',
81
+ defaultValue: 'finite',
82
+ );
83
+
84
+ /// 是否启用隐私协议对话框
85
+ static const bool enableAgreementDialog = bool.fromEnvironment(
86
+ 'ENABLE_AGREEMENT_DIALOG',
87
+ defaultValue: true,
88
+ );
89
+
90
+ /// 隐私协议版本
91
+ static const String agreementVersion = String.fromEnvironment(
92
+ 'AGREEMENT_VERSION',
93
+ defaultValue: '1.0.0',
94
+ );
95
+
96
+ /// 用户协议 URL
97
+ static const String userAgreementUrl = String.fromEnvironment(
98
+ 'USER_AGREEMENT_URL',
99
+ defaultValue: 'https://example.com/agreement',
100
+ );
101
+
102
+ /// 隐私政策 URL
103
+ static const String privacyPolicyUrl = String.fromEnvironment(
104
+ 'PRIVACY_POLICY_URL',
105
+ defaultValue: 'https://example.com/privacy',
106
+ );
107
+ }
@@ -1,11 +1,11 @@
1
-
2
-
3
1
  {{#if_network}}
4
2
  import '../network/app_http.dart';
5
3
  import '../network/interceptors/index.dart';
6
4
  import '../network/response_adapter.dart';
7
5
  {{/if_network}}
6
+
8
7
  import 'app_config.dart';
8
+ import 'app_env.dart';
9
9
  /// 应用初始化器
10
10
  ///
11
11
  /// 统一管理应用启动前的所有初始化逻辑
@@ -23,15 +23,13 @@ class AppInitializer {
23
23
 
24
24
  /// 初始化应用
25
25
  static Future<void> init({
26
- String? apiBaseUrl,
27
- bool? enableLog,
28
- bool useMockData = false,
26
+ dynamic extra,
27
+ bool? useMockData,
29
28
  }) async {
30
29
  // 1. 初始化应用配置
31
30
  await AppConfig.init(
32
- apiBaseUrl: apiBaseUrl,
33
- enableLog: enableLog,
34
- useMockData: useMockData,
31
+ useMockData: useMockData ?? EnvConfig.useMockData,
32
+ extra: extra,
35
33
  );
36
34
 
37
35
  {{#if_network}}
@@ -46,26 +44,21 @@ class AppInitializer {
46
44
  /// 初始化网络层配置
47
45
  static void _initNetwork() {
48
46
  AppHttp.init(
49
- // 使用图虫 API 适配器 {result, message, feedList}
50
- adapter: TuChongAdapter(),
47
+ // 默认使用通用 {code, msg/message, data} 响应适配器。
48
+ adapter: DefaultResponseAdapter(),
51
49
 
52
50
  // 自动显示错误弹窗 (默认 true)
53
51
  autoShowError: true,
54
-
55
- interceptors: [
56
- // Token 自动注入
57
- AuthInterceptor(
58
- getToken: () {
59
- // 从本地存储获取 token
60
- // return StorageUtil.getString('token') ?? '';
61
- return '';
62
- },
63
- ),
52
+ globalParams: () => {
53
+ 'appVersion': AppConfig.I.currentVersion,
54
+ 'appEnv': AppConfig.I.env.name,
55
+ },
56
+ interceptorBuilder: (dio) => [
57
+ {{AUTH_INTERCEPTOR_BLOCK}}
64
58
  // 请求重试
65
59
  RetryInterceptor(
66
- dio: AppHttp().dio,
67
- maxRetries: 3,
68
- retryDelay: const Duration(seconds: 1),
60
+ dio: dio,
61
+ maxRetries: 2,
69
62
  ),
70
63
  ],
71
64
  );
@@ -1,3 +1,6 @@
1
1
  // 导出配置类 (按字母顺序排列)
2
+ export 'agreement_document_page.dart';
2
3
  export 'app_config.dart';
3
- export 'app_initializer.dart';
4
+ export 'app_env.dart';
5
+ export 'app_initializer.dart';
6
+ export 'privacy_dialog.dart';
@@ -0,0 +1,158 @@
1
+ import 'package:flutter/gestures.dart';
2
+ import 'package:flutter/material.dart';
3
+
4
+ import '../index.dart';
5
+
6
+ /// 隐私协议确认弹窗
7
+ class PrivacyDialog extends StatelessWidget {
8
+ const PrivacyDialog({super.key});
9
+
10
+ /// 显示弹窗
11
+ static Future<bool?> show(BuildContext context) {
12
+ return showDialog<bool>(
13
+ context: context,
14
+ barrierDismissible: false,
15
+ barrierColor: Colors.black.withOpacity(0.32),
16
+ builder: (context) => const PrivacyDialog(),
17
+ );
18
+ }
19
+
20
+ @override
21
+ Widget build(BuildContext context) {
22
+ final titleStyle = AppTextStyleConfig.I.titleSmall();
23
+ final contentStyle = AppTextStyleConfig.I.body();
24
+ final linkStyle = AppTextStyleConfig.I.body().copyWith(
25
+ color: AppColorConfig.I.primary,
26
+ );
27
+
28
+ return PopScope(
29
+ canPop: false,
30
+ child: Center(
31
+ child: Container(
32
+ margin: const EdgeInsets.symmetric(horizontal: 32),
33
+ padding: const EdgeInsets.all(24),
34
+ decoration: BoxDecoration(
35
+ color: Colors.white,
36
+ borderRadius: BorderRadius.circular(16),
37
+ boxShadow: const [
38
+ BoxShadow(
39
+ color: Color(0x0D000000),
40
+ blurRadius: 24,
41
+ offset: Offset(0, 8),
42
+ ),
43
+ ],
44
+ ),
45
+ child: Column(
46
+ mainAxisSize: MainAxisSize.min,
47
+ children: [
48
+ Text('用户协议与隐私政策', style: titleStyle),
49
+ const SizedBox(height: 20),
50
+ ConstrainedBox(
51
+ constraints: const BoxConstraints(maxHeight: 320),
52
+ child: SingleChildScrollView(
53
+ child: RichText(
54
+ text: TextSpan(
55
+ style: contentStyle.copyWith(height: 1.6),
56
+ children: [
57
+ const TextSpan(
58
+ text: '欢迎使用 ${EnvConfig.appName}!在您使用前,请您务必仔细阅读',
59
+ ),
60
+ TextSpan(
61
+ text: '《用户协议》',
62
+ style: linkStyle,
63
+ recognizer: TapGestureRecognizer()
64
+ ..onTap = () {
65
+ _openAgreementDocument(
66
+ context,
67
+ AgreementDocumentType.userAgreement,
68
+ );
69
+ },
70
+ ),
71
+ const TextSpan(text: '与'),
72
+ TextSpan(
73
+ text: '《隐私政策》',
74
+ style: linkStyle,
75
+ recognizer: TapGestureRecognizer()
76
+ ..onTap = () {
77
+ _openAgreementDocument(
78
+ context,
79
+ AgreementDocumentType.privacyPolicy,
80
+ );
81
+ },
82
+ ),
83
+ const TextSpan(
84
+ text: '。我们将严格按照政策规定保护您的个人信息。\n\n'
85
+ '1. 我们会收集您的设备信息用于基础统计。\n'
86
+ '2. 为了提供更好的服务,我们需要部分系统权限。\n\n'
87
+ '如果您同意上述协议,请点击“同意”开始使用。',
88
+ ),
89
+ ],
90
+ ),
91
+ ),
92
+ ),
93
+ ),
94
+ const SizedBox(height: 24),
95
+ Row(
96
+ children: [
97
+ Expanded(
98
+ child: OutlinedButton(
99
+ onPressed: () {
100
+ Navigator.pop(context, false);
101
+ },
102
+ style: OutlinedButton.styleFrom(
103
+ padding: const EdgeInsets.symmetric(vertical: 12),
104
+ side: BorderSide(color: AppColorConfig.I.divider),
105
+ shape: RoundedRectangleBorder(
106
+ borderRadius: BorderRadius.circular(12),
107
+ ),
108
+ ),
109
+ child: Text(
110
+ '不同意并退出',
111
+ style: AppTextStyleConfig.I.body().copyWith(
112
+ color: AppColorConfig.I.titleSecondary,
113
+ ),
114
+ ),
115
+ ),
116
+ ),
117
+ const SizedBox(width: 16),
118
+ Expanded(
119
+ child: ElevatedButton(
120
+ onPressed: () {
121
+ Navigator.pop(context, true);
122
+ },
123
+ style: ElevatedButton.styleFrom(
124
+ backgroundColor: AppColorConfig.I.primary,
125
+ padding: const EdgeInsets.symmetric(vertical: 12),
126
+ shape: RoundedRectangleBorder(
127
+ borderRadius: BorderRadius.circular(12),
128
+ ),
129
+ ),
130
+ child: Text(
131
+ '同意',
132
+ style: AppTextStyleConfig.I.body().copyWith(
133
+ color: Colors.white,
134
+ ),
135
+ ),
136
+ ),
137
+ ),
138
+ ],
139
+ ),
140
+ ],
141
+ ),
142
+ ),
143
+ ),
144
+ );
145
+ }
146
+
147
+ Future<void> _openAgreementDocument(
148
+ BuildContext context,
149
+ AgreementDocumentType type,
150
+ ) async {
151
+ Navigator.push(
152
+ context,
153
+ MaterialPageRoute(
154
+ builder: (context) => AgreementDocumentPage(type: type),
155
+ ),
156
+ );
157
+ }
158
+ }
@@ -1,4 +1,7 @@
1
1
  // 导出核心模块 (按字母顺序排列)
2
+ {{#if_auth}}
3
+ export 'auth/index.dart';
4
+ {{/if_auth}}
2
5
  export 'base/index.dart';
3
6
  export 'config/index.dart';
4
7
  export 'network/index.dart';
@@ -0,0 +1,6 @@
1
+ import 'package:flutter/widgets.dart';
2
+
3
+ mixin KeepAliveMixin<T extends StatefulWidget> on State<T> {
4
+ @override
5
+ bool get wantKeepAlive => true;
6
+ }
@@ -0,0 +1,7 @@
1
+ mixin ScrollControllerMixin {
2
+ double lastOffset = 0;
3
+
4
+ void cacheScrollOffset(double offset) {
5
+ lastOffset = offset;
6
+ }
7
+ }
@@ -0,0 +1,18 @@
1
+ mixin RequestGuardMixin {
2
+ bool _requestInFlight = false;
3
+
4
+ bool get hasRequestInFlight => _requestInFlight;
5
+
6
+ Future<T> guardRequest<T>(Future<T> Function() action) async {
7
+ if (_requestInFlight) {
8
+ throw StateError('A guarded request is already running.');
9
+ }
10
+
11
+ _requestInFlight = true;
12
+ try {
13
+ return await action();
14
+ } finally {
15
+ _requestInFlight = false;
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ import 'dart:async';
2
+
3
+ mixin DebounceMixin {
4
+ Timer? _debounceTimer;
5
+
6
+ void debounce({
7
+ required Duration duration,
8
+ required void Function() action,
9
+ }) {
10
+ _debounceTimer?.cancel();
11
+ _debounceTimer = Timer(duration, action);
12
+ }
13
+
14
+ void disposeDebounceMixin() {
15
+ _debounceTimer?.cancel();
16
+ _debounceTimer = null;
17
+ }
18
+ }
@@ -40,6 +40,8 @@ class AppHttp {
40
40
  static void init({
41
41
  AppResponseAdapter? adapter,
42
42
  List<Interceptor>? interceptors,
43
+ List<Interceptor> Function(Dio dio)? interceptorBuilder,
44
+ Map<String, dynamic> Function()? globalParams,
43
45
  bool autoShowError = true,
44
46
  }) {
45
47
  final instance = AppHttp._instance;
@@ -58,7 +60,14 @@ class AppHttp {
58
60
  ),
59
61
  );
60
62
 
61
- // 1. 添加日志拦截器(完整输出,不截断)
63
+ // 1. 添加全局请求参数拦截器,保证日志记录的是最终请求。
64
+ if (globalParams != null) {
65
+ instance._dio.interceptors.add(
66
+ GlobalParamsInterceptor(getParams: globalParams),
67
+ );
68
+ }
69
+
70
+ // 2. 添加日志拦截器(完整输出,不截断)
62
71
  if (config.enableLog) {
63
72
  instance._dio.interceptors.add(
64
73
  FullLogInterceptor(
@@ -68,7 +77,7 @@ class AppHttp {
68
77
  );
69
78
  }
70
79
 
71
- // 2. 添加响应处理拦截器 (核心)
80
+ // 3. 添加响应处理拦截器 (核心)
72
81
  instance._dio.interceptors.add(
73
82
  AppResponseInterceptor(
74
83
  adapter: adapter ?? DefaultResponseAdapter(),
@@ -76,12 +85,18 @@ class AppHttp {
76
85
  ),
77
86
  );
78
87
 
79
- // 2.5 添加网络错误拦截器
88
+ // 4. 添加网络错误拦截器
80
89
  instance._dio.interceptors.add(
81
90
  NetworkErrorInterceptor(autoShowError: autoShowError),
82
91
  );
83
92
 
84
- // 3. 添加自定义拦截器
93
+ // 5. 添加需要复用当前 Dio 实例的自定义拦截器。
94
+ final builtInterceptors = interceptorBuilder?.call(instance._dio);
95
+ if (builtInterceptors != null) {
96
+ instance._dio.interceptors.addAll(builtInterceptors);
97
+ }
98
+
99
+ // 6. 添加普通自定义拦截器。
85
100
  if (interceptors != null) {
86
101
  instance._dio.interceptors.addAll(interceptors);
87
102
  }
@@ -8,5 +8,9 @@ export 'app_http.dart';
8
8
  export 'app_response.dart';
9
9
  // 网络拦截器
10
10
  export 'interceptors/index.dart';
11
+ {{#if_network_monitor}}
12
+ // 网络状态监听
13
+ export 'network_monitor.dart';
14
+ {{/if_network_monitor}}
11
15
  // 网络响应适配器
12
16
  export 'response_adapter.dart';