flu-cli-core 1.0.4 → 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.
- package/README.md +22 -17
- package/dist/chunk-BGYZU6TU.js +466 -0
- package/dist/chunk-QGM4M3NI.js +37 -0
- package/dist/factory-LM2CTHPW.js +7 -0
- package/dist/factory-P6ABQFH3.js +7 -0
- package/dist/index.cjs +18901 -3313
- package/dist/index.d.cts +833 -72
- package/dist/index.d.ts +833 -72
- package/dist/index.js +18569 -3132
- package/dist/upgrade_snippets-BJ6CQY5Q.js +9 -0
- package/package.json +9 -4
- package/templates/README.md +12 -0
- package/templates/core_files/auth/auth_middleware.dart.template +33 -0
- package/templates/core_files/auth/auth_service.dart.template +22 -0
- package/templates/core_files/auth/auth_viewmodel_mixin.dart.template +9 -0
- package/templates/core_files/auth/index.dart.template +4 -0
- package/templates/core_files/base/base_service.dart.template +12 -0
- package/templates/core_files/base/index.dart.template +3 -0
- package/templates/core_files/config/agreement_document_page.dart.template +220 -0
- package/templates/core_files/config/app_agreement.dart.template +297 -0
- package/templates/core_files/config/app_config.dart.template +81 -22
- package/templates/core_files/config/app_env.dart.template +107 -0
- package/templates/core_files/config/app_initializer.dart.template +16 -23
- package/templates/core_files/config/index.dart.template +4 -1
- package/templates/core_files/config/privacy_dialog.dart.template +158 -0
- package/templates/core_files/index.dart.template +3 -0
- package/templates/core_files/mixins/page/keep_alive_mixin.dart.template +6 -0
- package/templates/core_files/mixins/page/scroll_controller_mixin.dart.template +7 -0
- package/templates/core_files/mixins/service/request_guard_mixin.dart.template +18 -0
- package/templates/core_files/mixins/viewmodel/debounce_mixin.dart.template +18 -0
- package/templates/core_files/network/app_http.dart.template +19 -4
- package/templates/core_files/network/index.dart.template +4 -0
- package/templates/core_files/network/interceptors/global_params_interceptor.dart.template +77 -0
- package/templates/core_files/network/interceptors/index.dart.template +3 -0
- package/templates/core_files/network/network_monitor.dart.template +18 -0
- package/templates/core_files/network/response_adapter.dart.template +8 -19
- package/templates/core_files/router/app_routes.dart.template +3 -6
- package/templates/core_files/storage/storage_keys.dart.template +6 -0
- package/templates/core_files/theme/app_color_config.dart.template +32 -0
- package/templates/core_files/theme/app_text_size_config.dart.template +22 -0
- package/templates/core_files/theme/app_text_style_config.dart.template +139 -0
- package/templates/core_files/theme/app_theme.dart.template +72 -12
- package/templates/core_files/theme/index.dart.template +3 -0
- package/templates/core_files/utils/loading_util.dart.template +1 -1
- package/templates/examples/eg_list_page.dart.template +1 -2
- package/templates/examples/eg_service.dart.template +27 -4
- package/templates/examples/home_feed_service.dart.template +37 -0
- package/templates/helper_examples/image_picker_example_page.dart.template +289 -0
- package/templates/helper_examples/index.dart.template +4 -0
- package/templates/helper_examples/payment_shell_example_page.dart.template +67 -0
- package/templates/helper_examples/permission_example_page.dart.template +365 -0
- package/templates/helper_examples/webview_example_page.dart.template +44 -0
- package/templates/helpers/image_picker/README.md.template +30 -0
- package/templates/helpers/image_picker/index.dart.template +73 -0
- package/templates/helpers/payment/README.md.template +29 -0
- package/templates/helpers/payment/index.dart.template +66 -0
- package/templates/helpers/permission/README.md.template +30 -0
- package/templates/helpers/permission/index.dart.template +67 -0
- package/templates/helpers/webview/README.md.template +29 -0
- package/templates/helpers/webview/index.dart.template +88 -0
- package/templates/snippets/flu-cli.code-snippets +35 -26
- package/templates/starter_project/.env.dev.template +14 -0
- package/templates/starter_project/.env.prod.example.template +14 -0
- package/templates/starter_project/.env.staging.template +14 -0
- package/templates/starter_project/.vscode/launch.json.template +54 -0
- package/templates/starter_project/DEVELOPER_GUIDE.md.template +150 -0
- package/templates/starter_project/README.md.template +99 -0
- package/templates/starter_project/analysis_options.yaml.template +28 -0
- package/templates/starter_project/lib/app.dart.template +22 -0
- package/templates/starter_project/lib/main.dart.template +34 -0
- package/templates/starter_project/lib/pages/splash_page.dart.template +154 -0
- package/templates/template_clean/lib/features/home/data/datasources/index.dart +1 -0
- package/templates/template_clean/lib/features/home/data/models/index.dart +1 -0
- package/templates/template_clean/lib/features/home/domain/index.dart +1 -0
- package/templates/template_clean/lib/features/home/presentation/pages/home_page.dart +290 -0
- package/templates/template_clean/lib/features/home/presentation/pages/index.dart +2 -0
- package/templates/template_clean/lib/features/home/presentation/pages/splash_page.dart +154 -0
- package/templates/template_clean/lib/features/home/presentation/viewmodels/home_viewmodel.dart +17 -0
- package/templates/template_clean/lib/features/index.dart +2 -0
- package/templates/template_clean/lib/features/user/data/datasources/home_feed_service.dart +37 -0
- package/templates/template_clean/lib/features/user/data/datasources/index.dart +4 -0
- package/templates/template_clean/lib/features/user/data/models/index.dart +3 -0
- package/templates/template_clean/lib/features/user/data/models/user.dart +15 -0
- package/templates/template_clean/lib/features/user/domain/index.dart +1 -0
- package/templates/template_clean/lib/features/user/presentation/pages/index.dart +1 -0
- package/templates/template_clean/lib/features/user/presentation/pages/user_list_page.dart +27 -0
- package/templates/template_clean/lib/features/user/presentation/viewmodels/user_list_viewmodel.dart +88 -0
- package/templates/template_clean/lib/features/user/presentation/widgets/user_item_card.dart +24 -0
- package/templates/template_clean/lib/shared/extensions/index.dart +1 -0
- package/templates/template_clean/lib/shared/widgets/index.dart +1 -0
- package/templates/template_lite/lib/models/index.dart +1 -0
- package/templates/template_lite/lib/pages/home_page.dart +290 -0
- package/templates/template_lite/lib/pages/index.dart +3 -0
- package/templates/template_lite/lib/pages/splash_page.dart +154 -0
- package/templates/template_lite/lib/pages/user_list_page.dart +29 -0
- package/templates/template_lite/lib/services/home_feed_service.dart +37 -0
- package/templates/template_lite/lib/services/index.dart +5 -0
- package/templates/template_lite/lib/utils/index.dart +1 -0
- package/templates/template_lite/lib/viewmodels/home_viewmodel.dart +34 -0
- package/templates/template_lite/lib/viewmodels/index.dart +2 -0
- package/templates/template_lite/lib/viewmodels/user_list_viewmodel.dart +103 -0
- package/templates/template_lite/lib/widgets/index.dart +1 -0
- package/templates/template_lite/lib/widgets/user_item_widget.dart +57 -0
- package/templates/template_modular/lib/features/home/index.dart +2 -0
- package/templates/template_modular/lib/features/home/models/index.dart +1 -0
- package/templates/template_modular/lib/features/home/pages/home_page.dart +290 -0
- package/templates/template_modular/lib/features/home/pages/index.dart +2 -0
- package/templates/template_modular/lib/features/home/pages/splash_page.dart +154 -0
- package/templates/template_modular/lib/features/home/services/index.dart +1 -0
- package/templates/template_modular/lib/features/home/viewmodels/home_viewmodel.dart +17 -0
- package/templates/template_modular/lib/features/index.dart +2 -0
- package/templates/template_modular/lib/features/user/index.dart +6 -0
- package/templates/template_modular/lib/features/user/pages/user_list_page.dart +26 -0
- package/templates/template_modular/lib/features/user/services/home_feed_service.dart +37 -0
- package/templates/template_modular/lib/features/user/viewmodels/user_list_viewmodel.dart +103 -0
- package/templates/template_modular/lib/features/user/widgets/user_item_widget.dart +24 -0
- package/templates/template_modular/lib/shared/utils/index.dart +1 -0
- package/templates/template_modular/lib/shared/widgets/index.dart +1 -0
|
@@ -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
|
+
}
|
|
@@ -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
|
-
//
|
|
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
|
-
//
|
|
88
|
+
// 4. 添加网络错误拦截器
|
|
80
89
|
instance._dio.interceptors.add(
|
|
81
90
|
NetworkErrorInterceptor(autoShowError: autoShowError),
|
|
82
91
|
);
|
|
83
92
|
|
|
84
|
-
//
|
|
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
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import 'package:dio/dio.dart';
|
|
2
|
+
import 'package:flutter/foundation.dart';
|
|
3
|
+
|
|
4
|
+
/// 全局参数自动注入拦截器。
|
|
5
|
+
class GlobalParamsInterceptor extends Interceptor {
|
|
6
|
+
GlobalParamsInterceptor({
|
|
7
|
+
required this.getParams,
|
|
8
|
+
this.injectToData = true,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
final Map<String, dynamic> Function() getParams;
|
|
12
|
+
final bool injectToData;
|
|
13
|
+
|
|
14
|
+
static bool _isGetLike(String? method) {
|
|
15
|
+
final m = (method ?? 'GET').toUpperCase();
|
|
16
|
+
return m == 'GET' || m == 'HEAD';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
void _mergeIntoQuery(RequestOptions options, Map<String, dynamic> params) {
|
|
20
|
+
final newQueryParams = Map<String, dynamic>.from(options.queryParameters);
|
|
21
|
+
newQueryParams.addAll(params);
|
|
22
|
+
options.queryParameters = newQueryParams;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
void _mergeIntoBody(RequestOptions options, Map<String, dynamic> params) {
|
|
26
|
+
if (options.data == null) {
|
|
27
|
+
options.data = Map<String, dynamic>.from(params);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (options.data is Map) {
|
|
31
|
+
final newData = Map<String, dynamic>.from(options.data as Map);
|
|
32
|
+
newData.addAll(params);
|
|
33
|
+
options.data = newData;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (options.data is FormData) {
|
|
37
|
+
final formData = options.data as FormData;
|
|
38
|
+
for (final entry in params.entries) {
|
|
39
|
+
formData.fields.add(MapEntry(entry.key, entry.value?.toString() ?? ''));
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
assert(() {
|
|
44
|
+
debugPrint(
|
|
45
|
+
'GlobalParamsInterceptor: 未注入全局参数,body 类型 '
|
|
46
|
+
'${options.data.runtimeType} 不支持合并',
|
|
47
|
+
);
|
|
48
|
+
return true;
|
|
49
|
+
}());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
|
54
|
+
try {
|
|
55
|
+
final params = getParams();
|
|
56
|
+
if (params.isEmpty) {
|
|
57
|
+
return handler.next(options);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (_isGetLike(options.method)) {
|
|
61
|
+
_mergeIntoQuery(options, params);
|
|
62
|
+
return handler.next(options);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (injectToData) {
|
|
66
|
+
_mergeIntoBody(options, params);
|
|
67
|
+
} else {
|
|
68
|
+
_mergeIntoQuery(options, params);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
handler.next(options);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
debugPrint('GlobalParamsInterceptor Error: $error');
|
|
74
|
+
handler.next(options);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export 'app_response_interceptor.dart';
|
|
2
|
+
{{#if_auth}}
|
|
2
3
|
export 'auth_interceptor.dart';
|
|
4
|
+
{{/if_auth}}
|
|
3
5
|
export 'error_interceptor.dart';
|
|
6
|
+
export 'global_params_interceptor.dart';
|
|
4
7
|
export 'log_interceptor.dart';
|
|
5
8
|
export 'network_error_interceptor.dart';
|
|
6
9
|
export 'retry_interceptor.dart';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import 'package:connectivity_plus/connectivity_plus.dart';
|
|
2
|
+
|
|
3
|
+
/// 提供最小网络状态监听封装,供项目初始化或全局状态层接入。
|
|
4
|
+
class NetworkMonitor {
|
|
5
|
+
NetworkMonitor({
|
|
6
|
+
Connectivity? connectivity,
|
|
7
|
+
}) : _connectivity = connectivity ?? Connectivity();
|
|
8
|
+
|
|
9
|
+
final Connectivity _connectivity;
|
|
10
|
+
|
|
11
|
+
Stream<List<ConnectivityResult>> watch() {
|
|
12
|
+
return _connectivity.onConnectivityChanged;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
Future<List<ConnectivityResult>> current() {
|
|
16
|
+
return _connectivity.checkConnectivity();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -13,6 +13,14 @@ abstract class AppResponseAdapter {
|
|
|
13
13
|
class DefaultResponseAdapter extends AppResponseAdapter {
|
|
14
14
|
@override
|
|
15
15
|
Map<String, dynamic> adapt(dynamic json) {
|
|
16
|
+
if (json is List) {
|
|
17
|
+
return {
|
|
18
|
+
'code': AppErrorCode.success,
|
|
19
|
+
'msg': '成功',
|
|
20
|
+
'data': json,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
if (json is Map) {
|
|
17
25
|
return {
|
|
18
26
|
'code': json['code'] ?? AppErrorCode.networkError,
|
|
@@ -48,22 +56,3 @@ Map<String, dynamic> _buildError(String msg) {
|
|
|
48
56
|
'data': null,
|
|
49
57
|
};
|
|
50
58
|
}
|
|
51
|
-
|
|
52
|
-
/// 图虫 API 适配器
|
|
53
|
-
/// 对应格式: { "result": "SUCCESS", "message": "...", "feedList": [...] }
|
|
54
|
-
class TuChongAdapter extends AppResponseAdapter {
|
|
55
|
-
@override
|
|
56
|
-
Map<String, dynamic> adapt(dynamic json) {
|
|
57
|
-
if (json is Map) {
|
|
58
|
-
final result = json['result'];
|
|
59
|
-
final isSuccess = result == 'SUCCESS';
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
'code': isSuccess ? 200 : -1,
|
|
63
|
-
'msg': json['message'] ?? '',
|
|
64
|
-
'data': json['feedList'] ?? json['data'],
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
return _buildError('数据格式错误');
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -12,8 +12,7 @@ class AppRoutes {
|
|
|
12
12
|
// 路由常量
|
|
13
13
|
static const String home = '/';
|
|
14
14
|
static const String splash = '/splash';
|
|
15
|
-
|
|
16
|
-
static const String userList = '/user-list';
|
|
15
|
+
// 网络示例路由 - 由 CLI 动态注入
|
|
17
16
|
|
|
18
17
|
// __ROUTE_CONFIG_START__
|
|
19
18
|
/// Material 路由映射
|
|
@@ -21,10 +20,8 @@ class AppRoutes {
|
|
|
21
20
|
static Map<String, WidgetBuilder> get routes {
|
|
22
21
|
return {
|
|
23
22
|
home: (context) => const HomePage(),
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
egList: (context) => const EgListPage(),
|
|
27
|
-
{{/if_network}}
|
|
23
|
+
splash: (context) => const SplashPage(),
|
|
24
|
+
// 网络示例路由映射 - 由 CLI 动态注入
|
|
28
25
|
// --- 在此下方添加您的自定义路由 ---
|
|
29
26
|
};
|
|
30
27
|
}
|
|
@@ -10,6 +10,12 @@ abstract class StorageKeys {
|
|
|
10
10
|
/// 当前应用版本(用于版本迁移)
|
|
11
11
|
static const appVersion = 'app_version';
|
|
12
12
|
|
|
13
|
+
/// 是否已同意隐私协议
|
|
14
|
+
static const privacyAgreed = 'app_privacy_agreed';
|
|
15
|
+
|
|
16
|
+
/// 已接受的协议版本
|
|
17
|
+
static const agreementAcceptedVersion = 'app_agreement_accepted_version';
|
|
18
|
+
|
|
13
19
|
// ==================== 用户相关 ====================
|
|
14
20
|
|
|
15
21
|
/// 用户 Token
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
/// 统一维护项目级颜色令牌,供 Theme 和文本样式配置复用。
|
|
4
|
+
class AppColorConfig {
|
|
5
|
+
static AppColorConfig get I => const AppColorConfig();
|
|
6
|
+
|
|
7
|
+
const AppColorConfig({
|
|
8
|
+
this.primary = const Color(0xFF3B82F6),
|
|
9
|
+
this.secondary = const Color(0xFF22C55E),
|
|
10
|
+
this.background = const Color(0xFFF8FAFC),
|
|
11
|
+
this.surface = Colors.white,
|
|
12
|
+
this.title = const Color(0xFF111827),
|
|
13
|
+
this.titleSecondary = const Color(0xFF4B5563),
|
|
14
|
+
this.caption = const Color(0xFF6B7280),
|
|
15
|
+
this.divider = const Color(0xFFE5E7EB),
|
|
16
|
+
this.error = const Color(0xFFEF4444),
|
|
17
|
+
this.success = const Color(0xFF22C55E),
|
|
18
|
+
this.warning = const Color(0xFFF59E0B),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
final Color primary;
|
|
22
|
+
final Color secondary;
|
|
23
|
+
final Color background;
|
|
24
|
+
final Color surface;
|
|
25
|
+
final Color title;
|
|
26
|
+
final Color titleSecondary;
|
|
27
|
+
final Color caption;
|
|
28
|
+
final Color divider;
|
|
29
|
+
final Color error;
|
|
30
|
+
final Color success;
|
|
31
|
+
final Color warning;
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/// 统一维护语义化字号,避免模板默认暴露 s12/s14 这类数字别名。
|
|
2
|
+
class AppTextSizeConfig {
|
|
3
|
+
const AppTextSizeConfig({
|
|
4
|
+
this.caption = 12,
|
|
5
|
+
this.bodySmall = 13,
|
|
6
|
+
this.body = 14,
|
|
7
|
+
this.bodyLarge = 16,
|
|
8
|
+
this.titleSmall = 18,
|
|
9
|
+
this.title = 20,
|
|
10
|
+
this.titleLarge = 24,
|
|
11
|
+
this.headline = 28,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
final double caption;
|
|
15
|
+
final double bodySmall;
|
|
16
|
+
final double body;
|
|
17
|
+
final double bodyLarge;
|
|
18
|
+
final double titleSmall;
|
|
19
|
+
final double title;
|
|
20
|
+
final double titleLarge;
|
|
21
|
+
final double headline;
|
|
22
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
import 'app_color_config.dart';
|
|
4
|
+
import 'app_text_size_config.dart';
|
|
5
|
+
|
|
6
|
+
/// 基于颜色和字号配置生成语义化文本样式,允许调用方按场景覆盖细节。
|
|
7
|
+
class AppTextStyleConfig {
|
|
8
|
+
const AppTextStyleConfig({
|
|
9
|
+
this.colors = const AppColorConfig(),
|
|
10
|
+
this.sizes = const AppTextSizeConfig(),
|
|
11
|
+
this.fontFamily,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
static AppTextStyleConfig get I => const AppTextStyleConfig();
|
|
15
|
+
|
|
16
|
+
final AppColorConfig colors;
|
|
17
|
+
final AppTextSizeConfig sizes;
|
|
18
|
+
final String? fontFamily;
|
|
19
|
+
|
|
20
|
+
TextStyle caption({
|
|
21
|
+
Color? color,
|
|
22
|
+
FontWeight? fontWeight,
|
|
23
|
+
double? height,
|
|
24
|
+
}) {
|
|
25
|
+
return _style(
|
|
26
|
+
fontSize: sizes.caption,
|
|
27
|
+
color: color ?? colors.caption,
|
|
28
|
+
fontWeight: fontWeight,
|
|
29
|
+
height: height,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
TextStyle bodySmall({
|
|
34
|
+
Color? color,
|
|
35
|
+
FontWeight? fontWeight,
|
|
36
|
+
double? height,
|
|
37
|
+
}) {
|
|
38
|
+
return _style(
|
|
39
|
+
fontSize: sizes.bodySmall,
|
|
40
|
+
color: color ?? colors.titleSecondary,
|
|
41
|
+
fontWeight: fontWeight,
|
|
42
|
+
height: height,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
TextStyle body({
|
|
47
|
+
Color? color,
|
|
48
|
+
FontWeight? fontWeight,
|
|
49
|
+
double? height,
|
|
50
|
+
}) {
|
|
51
|
+
return _style(
|
|
52
|
+
fontSize: sizes.body,
|
|
53
|
+
color: color ?? colors.title,
|
|
54
|
+
fontWeight: fontWeight,
|
|
55
|
+
height: height,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
TextStyle bodyLarge({
|
|
60
|
+
Color? color,
|
|
61
|
+
FontWeight? fontWeight,
|
|
62
|
+
double? height,
|
|
63
|
+
}) {
|
|
64
|
+
return _style(
|
|
65
|
+
fontSize: sizes.bodyLarge,
|
|
66
|
+
color: color ?? colors.title,
|
|
67
|
+
fontWeight: fontWeight,
|
|
68
|
+
height: height,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
TextStyle titleSmall({
|
|
73
|
+
Color? color,
|
|
74
|
+
FontWeight? fontWeight,
|
|
75
|
+
double? height,
|
|
76
|
+
}) {
|
|
77
|
+
return _style(
|
|
78
|
+
fontSize: sizes.titleSmall,
|
|
79
|
+
color: color ?? colors.title,
|
|
80
|
+
fontWeight: fontWeight ?? FontWeight.w600,
|
|
81
|
+
height: height,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
TextStyle title({
|
|
86
|
+
Color? color,
|
|
87
|
+
FontWeight? fontWeight,
|
|
88
|
+
double? height,
|
|
89
|
+
}) {
|
|
90
|
+
return _style(
|
|
91
|
+
fontSize: sizes.title,
|
|
92
|
+
color: color ?? colors.title,
|
|
93
|
+
fontWeight: fontWeight ?? FontWeight.w600,
|
|
94
|
+
height: height,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
TextStyle titleLarge({
|
|
99
|
+
Color? color,
|
|
100
|
+
FontWeight? fontWeight,
|
|
101
|
+
double? height,
|
|
102
|
+
}) {
|
|
103
|
+
return _style(
|
|
104
|
+
fontSize: sizes.titleLarge,
|
|
105
|
+
color: color ?? colors.title,
|
|
106
|
+
fontWeight: fontWeight ?? FontWeight.w700,
|
|
107
|
+
height: height,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
TextStyle headline({
|
|
112
|
+
Color? color,
|
|
113
|
+
FontWeight? fontWeight,
|
|
114
|
+
double? height,
|
|
115
|
+
}) {
|
|
116
|
+
return _style(
|
|
117
|
+
fontSize: sizes.headline,
|
|
118
|
+
color: color ?? colors.title,
|
|
119
|
+
fontWeight: fontWeight ?? FontWeight.w700,
|
|
120
|
+
height: height,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
TextStyle _style({
|
|
125
|
+
required double fontSize,
|
|
126
|
+
required Color color,
|
|
127
|
+
FontWeight? fontWeight,
|
|
128
|
+
double? height,
|
|
129
|
+
}) {
|
|
130
|
+
// 所有语义样式最终都落到同一个底层构造,避免模板里出现重复 TextStyle 拼装。
|
|
131
|
+
return TextStyle(
|
|
132
|
+
fontSize: fontSize,
|
|
133
|
+
color: color,
|
|
134
|
+
fontWeight: fontWeight,
|
|
135
|
+
height: height,
|
|
136
|
+
fontFamily: fontFamily,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|