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.
- package/README.md +9 -0
- 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 +17765 -3244
- package/dist/index.d.cts +783 -99
- package/dist/index.d.ts +783 -99
- package/dist/index.js +17322 -2942
- package/dist/upgrade_snippets-BJ6CQY5Q.js +9 -0
- package/package.json +3 -3
- 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/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,365 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
import 'package:{{projectName}}/helpers/permission/index.dart';
|
|
4
|
+
|
|
5
|
+
/// 权限申请示例页。
|
|
6
|
+
///
|
|
7
|
+
/// 示例页提供“申请前说明 -> 系统授权 -> 状态更新 -> 前往设置”的完整闭环。
|
|
8
|
+
class PermissionExamplePage extends StatefulWidget {
|
|
9
|
+
const PermissionExamplePage({super.key});
|
|
10
|
+
|
|
11
|
+
@override
|
|
12
|
+
State<PermissionExamplePage> createState() => _PermissionExamplePageState();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class _PermissionExampleItem {
|
|
16
|
+
const _PermissionExampleItem({
|
|
17
|
+
required this.permission,
|
|
18
|
+
required this.title,
|
|
19
|
+
required this.subtitle,
|
|
20
|
+
required this.icon,
|
|
21
|
+
required this.dialogTitle,
|
|
22
|
+
required this.dialogMessage,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
final AppPermission permission;
|
|
26
|
+
final String title;
|
|
27
|
+
final String subtitle;
|
|
28
|
+
final IconData icon;
|
|
29
|
+
final String dialogTitle;
|
|
30
|
+
final String dialogMessage;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class _PermissionExamplePageState extends State<PermissionExamplePage>
|
|
34
|
+
with WidgetsBindingObserver {
|
|
35
|
+
static const _items = [
|
|
36
|
+
_PermissionExampleItem(
|
|
37
|
+
permission: AppPermission.camera,
|
|
38
|
+
title: '相机权限',
|
|
39
|
+
subtitle: '用于拍照、扫码和头像上传等需要即时拍摄的场景。',
|
|
40
|
+
icon: Icons.photo_camera_outlined,
|
|
41
|
+
dialogTitle: '申请相机权限',
|
|
42
|
+
dialogMessage: '我们只会在你主动点击拍照或扫码时请求相机权限,不会在后台持续访问相机。',
|
|
43
|
+
),
|
|
44
|
+
_PermissionExampleItem(
|
|
45
|
+
permission: AppPermission.photos,
|
|
46
|
+
title: '相册权限',
|
|
47
|
+
subtitle: '用于从系统相册选择图片或读取图片资源。',
|
|
48
|
+
icon: Icons.photo_library_outlined,
|
|
49
|
+
dialogTitle: '申请相册权限',
|
|
50
|
+
dialogMessage: '我们只会在你主动选择图片时读取相册资源,不会扫描或上传无关内容。',
|
|
51
|
+
),
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
final _helper = const PermissionHelper();
|
|
55
|
+
final Map<AppPermission, PermissionGrantResult> _statuses = {};
|
|
56
|
+
AppPermission? _requestingPermission;
|
|
57
|
+
String? _lastFeedback;
|
|
58
|
+
|
|
59
|
+
@override
|
|
60
|
+
void initState() {
|
|
61
|
+
super.initState();
|
|
62
|
+
WidgetsBinding.instance.addObserver(this);
|
|
63
|
+
_refreshStatuses();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@override
|
|
67
|
+
void dispose() {
|
|
68
|
+
WidgetsBinding.instance.removeObserver(this);
|
|
69
|
+
super.dispose();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@override
|
|
73
|
+
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
74
|
+
if (state == AppLifecycleState.resumed) {
|
|
75
|
+
_refreshStatuses();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Future<void> _refreshStatuses() async {
|
|
80
|
+
final entries = await Future.wait(
|
|
81
|
+
_items.map((item) async {
|
|
82
|
+
final status = await _helper.check(item.permission);
|
|
83
|
+
return MapEntry(item.permission, status);
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
if (!mounted) return;
|
|
87
|
+
setState(() {
|
|
88
|
+
_statuses
|
|
89
|
+
..clear()
|
|
90
|
+
..addEntries(entries);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
Future<void> _requestPermission(_PermissionExampleItem item) async {
|
|
95
|
+
final confirmed = await showDialog<bool>(
|
|
96
|
+
context: context,
|
|
97
|
+
builder: (dialogContext) => AlertDialog(
|
|
98
|
+
title: Text(item.dialogTitle),
|
|
99
|
+
content: Text(item.dialogMessage),
|
|
100
|
+
actions: [
|
|
101
|
+
TextButton(
|
|
102
|
+
onPressed: () => Navigator.of(dialogContext).pop(false),
|
|
103
|
+
child: const Text('暂不授权'),
|
|
104
|
+
),
|
|
105
|
+
FilledButton(
|
|
106
|
+
onPressed: () => Navigator.of(dialogContext).pop(true),
|
|
107
|
+
child: const Text('继续'),
|
|
108
|
+
),
|
|
109
|
+
],
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (confirmed != true) return;
|
|
114
|
+
|
|
115
|
+
setState(() => _requestingPermission = item.permission);
|
|
116
|
+
try {
|
|
117
|
+
final result = await _helper.request(item.permission);
|
|
118
|
+
await _refreshStatuses();
|
|
119
|
+
if (!mounted) return;
|
|
120
|
+
|
|
121
|
+
setState(() {
|
|
122
|
+
_requestingPermission = null;
|
|
123
|
+
_lastFeedback = _buildFeedback(item.title, result);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
final messenger = ScaffoldMessenger.of(context);
|
|
127
|
+
messenger
|
|
128
|
+
..hideCurrentSnackBar()
|
|
129
|
+
..showSnackBar(SnackBar(content: Text(_lastFeedback!)));
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (!mounted) return;
|
|
132
|
+
setState(() {
|
|
133
|
+
_requestingPermission = null;
|
|
134
|
+
_lastFeedback = '${item.title} 申请失败,请检查平台权限配置或插件状态。';
|
|
135
|
+
});
|
|
136
|
+
final messenger = ScaffoldMessenger.of(context);
|
|
137
|
+
messenger
|
|
138
|
+
..hideCurrentSnackBar()
|
|
139
|
+
..showSnackBar(SnackBar(content: Text(_lastFeedback!)));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
Future<void> _openSettings(_PermissionExampleItem item) async {
|
|
144
|
+
final opened = await _helper.openSettings();
|
|
145
|
+
if (!mounted) return;
|
|
146
|
+
final messenger = ScaffoldMessenger.of(context);
|
|
147
|
+
messenger
|
|
148
|
+
..hideCurrentSnackBar()
|
|
149
|
+
..showSnackBar(
|
|
150
|
+
SnackBar(
|
|
151
|
+
content: Text(
|
|
152
|
+
opened
|
|
153
|
+
? '已打开系统设置,返回应用后会自动刷新 ${item.title} 状态。'
|
|
154
|
+
: '当前设备无法直接打开系统设置,请手动前往设置检查 ${item.title}。',
|
|
155
|
+
),
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
String _statusLabel(PermissionGrantResult? result) {
|
|
161
|
+
switch (result) {
|
|
162
|
+
case PermissionGrantResult.granted:
|
|
163
|
+
return '已授权';
|
|
164
|
+
case PermissionGrantResult.permanentlyDenied:
|
|
165
|
+
return '需去设置';
|
|
166
|
+
case PermissionGrantResult.restricted:
|
|
167
|
+
return '受限';
|
|
168
|
+
case PermissionGrantResult.denied:
|
|
169
|
+
return '未申请';
|
|
170
|
+
case null:
|
|
171
|
+
return '检查中';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
Color _statusColor(PermissionGrantResult? result, BuildContext context) {
|
|
176
|
+
final scheme = Theme.of(context).colorScheme;
|
|
177
|
+
switch (result) {
|
|
178
|
+
case PermissionGrantResult.granted:
|
|
179
|
+
return Colors.green.shade600;
|
|
180
|
+
case PermissionGrantResult.permanentlyDenied:
|
|
181
|
+
return scheme.error;
|
|
182
|
+
case PermissionGrantResult.restricted:
|
|
183
|
+
return Colors.orange.shade700;
|
|
184
|
+
case PermissionGrantResult.denied:
|
|
185
|
+
return scheme.primary;
|
|
186
|
+
case null:
|
|
187
|
+
return scheme.outline;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
String _buildFeedback(String permissionName, PermissionGrantResult result) {
|
|
192
|
+
switch (result) {
|
|
193
|
+
case PermissionGrantResult.granted:
|
|
194
|
+
return '$permissionName 已授权,现在可以继续对应功能。';
|
|
195
|
+
case PermissionGrantResult.permanentlyDenied:
|
|
196
|
+
return '$permissionName 已被永久拒绝,请前往系统设置手动开启。';
|
|
197
|
+
case PermissionGrantResult.restricted:
|
|
198
|
+
return '$permissionName 当前受系统限制,可能需要检查设备策略或家长控制。';
|
|
199
|
+
case PermissionGrantResult.denied:
|
|
200
|
+
return '$permissionName 尚未授权,你可以稍后再次发起申请。';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
Widget _buildPermissionCard(_PermissionExampleItem item) {
|
|
205
|
+
final status = _statuses[item.permission];
|
|
206
|
+
final color = _statusColor(status, context);
|
|
207
|
+
final isBusy = _requestingPermission == item.permission;
|
|
208
|
+
final shouldOpenSettings = status == PermissionGrantResult.permanentlyDenied;
|
|
209
|
+
|
|
210
|
+
return Container(
|
|
211
|
+
margin: const EdgeInsets.only(bottom: 14),
|
|
212
|
+
padding: const EdgeInsets.all(16),
|
|
213
|
+
decoration: BoxDecoration(
|
|
214
|
+
color: Colors.white,
|
|
215
|
+
borderRadius: BorderRadius.circular(18),
|
|
216
|
+
border: Border.all(color: const Color(0xFFE5E7EB)),
|
|
217
|
+
boxShadow: const [
|
|
218
|
+
BoxShadow(
|
|
219
|
+
color: Color(0x080F172A),
|
|
220
|
+
blurRadius: 16,
|
|
221
|
+
offset: Offset(0, 8),
|
|
222
|
+
),
|
|
223
|
+
],
|
|
224
|
+
),
|
|
225
|
+
child: Column(
|
|
226
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
227
|
+
children: [
|
|
228
|
+
Row(
|
|
229
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
230
|
+
children: [
|
|
231
|
+
Container(
|
|
232
|
+
width: 44,
|
|
233
|
+
height: 44,
|
|
234
|
+
decoration: BoxDecoration(
|
|
235
|
+
color: color.withOpacity(0.12),
|
|
236
|
+
borderRadius: BorderRadius.circular(14),
|
|
237
|
+
),
|
|
238
|
+
child: Icon(item.icon, color: color),
|
|
239
|
+
),
|
|
240
|
+
const SizedBox(width: 14),
|
|
241
|
+
Expanded(
|
|
242
|
+
child: Column(
|
|
243
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
244
|
+
children: [
|
|
245
|
+
Text(
|
|
246
|
+
item.title,
|
|
247
|
+
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
|
|
248
|
+
),
|
|
249
|
+
const SizedBox(height: 4),
|
|
250
|
+
Text(
|
|
251
|
+
item.subtitle,
|
|
252
|
+
style: const TextStyle(
|
|
253
|
+
fontSize: 13,
|
|
254
|
+
height: 1.45,
|
|
255
|
+
color: Color(0xFF475569),
|
|
256
|
+
),
|
|
257
|
+
),
|
|
258
|
+
],
|
|
259
|
+
),
|
|
260
|
+
),
|
|
261
|
+
const SizedBox(width: 12),
|
|
262
|
+
Container(
|
|
263
|
+
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
|
264
|
+
decoration: BoxDecoration(
|
|
265
|
+
color: color.withOpacity(0.12),
|
|
266
|
+
borderRadius: BorderRadius.circular(999),
|
|
267
|
+
),
|
|
268
|
+
child: Text(
|
|
269
|
+
_statusLabel(status),
|
|
270
|
+
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700, color: color),
|
|
271
|
+
),
|
|
272
|
+
),
|
|
273
|
+
],
|
|
274
|
+
),
|
|
275
|
+
const SizedBox(height: 14),
|
|
276
|
+
Text(
|
|
277
|
+
'申请前会先弹出用途说明,只有你点击同意后才会继续触发系统授权。',
|
|
278
|
+
style: TextStyle(fontSize: 12, color: Colors.grey.shade600, height: 1.45),
|
|
279
|
+
),
|
|
280
|
+
const SizedBox(height: 14),
|
|
281
|
+
Row(
|
|
282
|
+
children: [
|
|
283
|
+
Expanded(
|
|
284
|
+
child: FilledButton(
|
|
285
|
+
onPressed: isBusy || shouldOpenSettings ? null : () => _requestPermission(item),
|
|
286
|
+
child: Text(isBusy ? '申请中...' : '申请授权'),
|
|
287
|
+
),
|
|
288
|
+
),
|
|
289
|
+
const SizedBox(width: 10),
|
|
290
|
+
Expanded(
|
|
291
|
+
child: OutlinedButton(
|
|
292
|
+
onPressed: shouldOpenSettings ? () => _openSettings(item) : _refreshStatuses,
|
|
293
|
+
child: Text(shouldOpenSettings ? '前往设置' : '刷新状态'),
|
|
294
|
+
),
|
|
295
|
+
),
|
|
296
|
+
],
|
|
297
|
+
),
|
|
298
|
+
],
|
|
299
|
+
),
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
@override
|
|
304
|
+
Widget build(BuildContext context) {
|
|
305
|
+
return Scaffold(
|
|
306
|
+
appBar: AppBar(title: const Text('权限申请示例')),
|
|
307
|
+
body: ListView(
|
|
308
|
+
padding: const EdgeInsets.all(20),
|
|
309
|
+
children: [
|
|
310
|
+
Container(
|
|
311
|
+
padding: const EdgeInsets.all(18),
|
|
312
|
+
decoration: BoxDecoration(
|
|
313
|
+
color: const Color(0xFFF8FAFC),
|
|
314
|
+
borderRadius: BorderRadius.circular(20),
|
|
315
|
+
),
|
|
316
|
+
child: const Column(
|
|
317
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
318
|
+
children: [
|
|
319
|
+
Text(
|
|
320
|
+
'权限申请最小闭环',
|
|
321
|
+
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w800),
|
|
322
|
+
),
|
|
323
|
+
SizedBox(height: 10),
|
|
324
|
+
Text(
|
|
325
|
+
'示例会先展示申请原因,再由用户确认是否继续授权;授权结果会实时回写到列表状态,永久拒绝时会引导前往系统设置。',
|
|
326
|
+
style: TextStyle(fontSize: 14, height: 1.55, color: Color(0xFF475569)),
|
|
327
|
+
),
|
|
328
|
+
],
|
|
329
|
+
),
|
|
330
|
+
),
|
|
331
|
+
const SizedBox(height: 20),
|
|
332
|
+
..._items.map(_buildPermissionCard),
|
|
333
|
+
if (_lastFeedback != null) ...[
|
|
334
|
+
const SizedBox(height: 6),
|
|
335
|
+
Container(
|
|
336
|
+
padding: const EdgeInsets.all(14),
|
|
337
|
+
decoration: BoxDecoration(
|
|
338
|
+
color: const Color(0xFFF8FAFC),
|
|
339
|
+
borderRadius: BorderRadius.circular(16),
|
|
340
|
+
),
|
|
341
|
+
child: Row(
|
|
342
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
343
|
+
children: [
|
|
344
|
+
const Icon(Icons.info_outline, size: 18, color: Color(0xFF475569)),
|
|
345
|
+
const SizedBox(width: 10),
|
|
346
|
+
Expanded(
|
|
347
|
+
child: Text(
|
|
348
|
+
_lastFeedback!,
|
|
349
|
+
style: const TextStyle(fontSize: 13, height: 1.5, color: Color(0xFF334155)),
|
|
350
|
+
),
|
|
351
|
+
),
|
|
352
|
+
],
|
|
353
|
+
),
|
|
354
|
+
),
|
|
355
|
+
],
|
|
356
|
+
const SizedBox(height: 10),
|
|
357
|
+
const Text(
|
|
358
|
+
'权限声明、用途文案与平台落点说明可查看 lib/helpers/README.platform-permissions.md。',
|
|
359
|
+
style: TextStyle(fontSize: 12, height: 1.5, color: Color(0xFF64748B)),
|
|
360
|
+
),
|
|
361
|
+
],
|
|
362
|
+
),
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
import 'package:{{projectName}}/helpers/webview/index.dart';
|
|
4
|
+
|
|
5
|
+
/// WebView 示例页。
|
|
6
|
+
///
|
|
7
|
+
/// 示例只负责展示入口;真正的 WebView 打开能力由 lib/helpers/webview 提供。
|
|
8
|
+
class WebviewExamplePage extends StatelessWidget {
|
|
9
|
+
const WebviewExamplePage({super.key});
|
|
10
|
+
|
|
11
|
+
static const _demoUrl = 'https://huozhiye.cn/flu-cli';
|
|
12
|
+
|
|
13
|
+
@override
|
|
14
|
+
Widget build(BuildContext context) {
|
|
15
|
+
return Scaffold(
|
|
16
|
+
appBar: AppBar(title: const Text('WebView 示例')),
|
|
17
|
+
body: ListView(
|
|
18
|
+
padding: const EdgeInsets.all(20),
|
|
19
|
+
children: [
|
|
20
|
+
const Text(
|
|
21
|
+
'WebView 功能已生成默认闭环,可直接打开网页。',
|
|
22
|
+
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700),
|
|
23
|
+
),
|
|
24
|
+
const SizedBox(height: 12),
|
|
25
|
+
const Text('默认示例会打开 Flu CLI 官方文档,业务项目可以在 lib/helpers/webview 中扩展 header、登录态或外部浏览器策略。'),
|
|
26
|
+
const SizedBox(height: 20),
|
|
27
|
+
FilledButton.icon(
|
|
28
|
+
onPressed: () {
|
|
29
|
+
const WebviewHelper().open(
|
|
30
|
+
context,
|
|
31
|
+
const WebviewRequest(
|
|
32
|
+
url: _demoUrl,
|
|
33
|
+
title: 'Flu CLI',
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
icon: const Icon(Icons.open_in_browser),
|
|
38
|
+
label: const Text('打开 Flu CLI 官方文档'),
|
|
39
|
+
),
|
|
40
|
+
],
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Image Picker Helper
|
|
2
|
+
|
|
3
|
+
图片选择 Helper 提供项目级图片选择能力,生成后已包含 `image_picker` 默认实现。
|
|
4
|
+
|
|
5
|
+
## 生成定位
|
|
6
|
+
|
|
7
|
+
- 落点:`lib/helpers/image_picker`
|
|
8
|
+
- 边界:不属于 Core,不从 `lib/core/index.dart` 导出
|
|
9
|
+
- 普通 Flutter 依赖:`image_picker`
|
|
10
|
+
- 最小闭环:`ImagePickerHelper().pick(...)` 可直接从相册或相机选择图片
|
|
11
|
+
- 示例入口:只有选择 `imagePickerBasic` 时,首页才展示图片选择示例入口
|
|
12
|
+
|
|
13
|
+
## 平台权限
|
|
14
|
+
|
|
15
|
+
- Android:CLI 会默认写入相机与图片读取权限,落点 `android/app/src/main/AndroidManifest.xml`。
|
|
16
|
+
- iOS:CLI 会默认补 `ios/Runner/Info.plist` 中的 `NSCameraUsageDescription`、`NSPhotoLibraryUsageDescription`。
|
|
17
|
+
- 修改入口:统一查看 `lib/helpers/README.platform-permissions.md`,按真实业务文案调整权限用途说明。
|
|
18
|
+
- Web:受浏览器文件选择能力影响,不同浏览器表现可能不同。
|
|
19
|
+
|
|
20
|
+
{{#if_harmony}}
|
|
21
|
+
## Harmony 注意
|
|
22
|
+
|
|
23
|
+
当前项目包含 ohos 平台。CLI 会默认尝试在 `ohos/entry/src/main/module.json5` 的 `requestPermissions` 中补相机 / 相册读取权限;图片选择插件依赖和实际权限声明仍需按当前 Harmony Flutter SDK 的三方插件规范确认。
|
|
24
|
+
|
|
25
|
+
鸿蒙插件适配参考:
|
|
26
|
+
|
|
27
|
+
- Flutter packages 适配仓库:https://gitcode.com/openharmony-tpc/flutter_packages
|
|
28
|
+
- Plus plugins 适配仓库:https://gitcode.com/openharmony-sig/flutter_plus_plugins
|
|
29
|
+
- 指导参考:https://gitee.com/openharmony-sig/flutter_plus_plugins/blob/master/GUIDE_DOCUMENT.md
|
|
30
|
+
{{/if_harmony}}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import 'dart:typed_data';
|
|
2
|
+
|
|
3
|
+
import 'package:image_picker/image_picker.dart' as image_picker;
|
|
4
|
+
|
|
5
|
+
/// 图片来源。
|
|
6
|
+
enum ImagePickerSource {
|
|
7
|
+
camera,
|
|
8
|
+
gallery,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/// 图片选择参数。
|
|
12
|
+
class ImagePickerRequest {
|
|
13
|
+
const ImagePickerRequest({
|
|
14
|
+
this.source = ImagePickerSource.gallery,
|
|
15
|
+
this.maxCount = 1,
|
|
16
|
+
this.compressQuality,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
final ImagePickerSource source;
|
|
20
|
+
final int maxCount;
|
|
21
|
+
final int? compressQuality;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// 图片选择结果。
|
|
25
|
+
class PickedImageAsset {
|
|
26
|
+
const PickedImageAsset({
|
|
27
|
+
required this.path,
|
|
28
|
+
this.name,
|
|
29
|
+
this.mimeType,
|
|
30
|
+
this.size,
|
|
31
|
+
this.bytes,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
final String path;
|
|
35
|
+
final String? name;
|
|
36
|
+
final String? mimeType;
|
|
37
|
+
final int? size;
|
|
38
|
+
final Uint8List? bytes;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Image picker helper 入口。
|
|
42
|
+
///
|
|
43
|
+
/// 默认实现使用 image_picker,生成后即可在 Android/iOS/Web 的常规插件能力
|
|
44
|
+
/// 范围内运行;Harmony 场景按 README 替换插件源或适配层。
|
|
45
|
+
class ImagePickerHelper {
|
|
46
|
+
ImagePickerHelper({image_picker.ImagePicker? picker})
|
|
47
|
+
: _picker = picker ?? image_picker.ImagePicker();
|
|
48
|
+
|
|
49
|
+
final image_picker.ImagePicker _picker;
|
|
50
|
+
|
|
51
|
+
/// 选择图片。
|
|
52
|
+
Future<List<PickedImageAsset>> pick(ImagePickerRequest request) async {
|
|
53
|
+
final source = request.source == ImagePickerSource.camera
|
|
54
|
+
? image_picker.ImageSource.camera
|
|
55
|
+
: image_picker.ImageSource.gallery;
|
|
56
|
+
final image = await _picker.pickImage(
|
|
57
|
+
source: source,
|
|
58
|
+
imageQuality: request.compressQuality,
|
|
59
|
+
);
|
|
60
|
+
if (image == null) return const [];
|
|
61
|
+
final size = await image.length();
|
|
62
|
+
final bytes = await image.readAsBytes();
|
|
63
|
+
return [
|
|
64
|
+
PickedImageAsset(
|
|
65
|
+
path: image.path,
|
|
66
|
+
name: image.name,
|
|
67
|
+
mimeType: image.mimeType,
|
|
68
|
+
size: size,
|
|
69
|
+
bytes: bytes,
|
|
70
|
+
),
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Payment Helper
|
|
2
|
+
|
|
3
|
+
支付 Helper 只提供项目级统一入口,不绑定任何具体支付 SDK。
|
|
4
|
+
|
|
5
|
+
## 生成定位
|
|
6
|
+
|
|
7
|
+
- 落点:`lib/helpers/payment`
|
|
8
|
+
- 边界:不属于 Core,不从 `lib/core/index.dart` 导出
|
|
9
|
+
- 适用:微信/支付宝/应用内购买/鸿蒙渠道支付等支付能力的统一适配层
|
|
10
|
+
- 例外:Payment 只生成协议、说明和壳子,不承诺真实支付 SDK 最小闭环
|
|
11
|
+
- 示例入口:只有选择 `paymentShell` 时,首页才展示支付协议壳示例
|
|
12
|
+
|
|
13
|
+
## 接入方式
|
|
14
|
+
|
|
15
|
+
1. 根据项目渠道选择真实支付 SDK。
|
|
16
|
+
2. 实现 `PaymentExecutor`。
|
|
17
|
+
3. 通过 `PaymentHelper(executor: yourExecutor)` 注入项目自己的支付逻辑。
|
|
18
|
+
|
|
19
|
+
{{#if_harmony}}
|
|
20
|
+
## Harmony 注意
|
|
21
|
+
|
|
22
|
+
当前项目包含 ohos 平台。支付 SDK 需要按鸿蒙渠道与当前 Harmony Flutter SDK 的插件规范单独确认,CLI 不会自动写入普通 Flutter 支付插件依赖。
|
|
23
|
+
|
|
24
|
+
鸿蒙插件适配参考:
|
|
25
|
+
|
|
26
|
+
- Flutter packages 适配仓库:https://gitcode.com/openharmony-tpc/flutter_packages
|
|
27
|
+
- Plus plugins 适配仓库:https://gitcode.com/openharmony-sig/flutter_plus_plugins
|
|
28
|
+
- 指导参考:https://gitee.com/openharmony-sig/flutter_plus_plugins/blob/master/GUIDE_DOCUMENT.md
|
|
29
|
+
{{/if_harmony}}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/// 支付 Helper 入口。
|
|
2
|
+
///
|
|
3
|
+
/// 这里只定义统一适配协议,不绑定任何支付 SDK。
|
|
4
|
+
/// 具体的微信、支付宝、IAP 或鸿蒙渠道支付由业务侧实现 [PaymentExecutor]。
|
|
5
|
+
typedef PaymentExecutor = Future<PaymentResult> Function(PaymentRequest request);
|
|
6
|
+
|
|
7
|
+
/// 支付渠道。
|
|
8
|
+
enum PaymentChannel {
|
|
9
|
+
wechat,
|
|
10
|
+
alipay,
|
|
11
|
+
iap,
|
|
12
|
+
harmony,
|
|
13
|
+
custom,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// 支付状态。
|
|
17
|
+
enum PaymentStatus {
|
|
18
|
+
success,
|
|
19
|
+
cancelled,
|
|
20
|
+
failed,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// 支付请求参数。
|
|
24
|
+
class PaymentRequest {
|
|
25
|
+
const PaymentRequest({
|
|
26
|
+
required this.channel,
|
|
27
|
+
required this.orderId,
|
|
28
|
+
this.payload = const <String, Object?>{},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
final PaymentChannel channel;
|
|
32
|
+
final String orderId;
|
|
33
|
+
final Map<String, Object?> payload;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// 支付结果。
|
|
37
|
+
class PaymentResult {
|
|
38
|
+
const PaymentResult({
|
|
39
|
+
required this.status,
|
|
40
|
+
this.message,
|
|
41
|
+
this.raw,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
final PaymentStatus status;
|
|
45
|
+
final String? message;
|
|
46
|
+
final Object? raw;
|
|
47
|
+
|
|
48
|
+
bool get isSuccess => status == PaymentStatus.success;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class PaymentHelper {
|
|
52
|
+
const PaymentHelper({PaymentExecutor? executor}) : _executor = executor;
|
|
53
|
+
|
|
54
|
+
final PaymentExecutor? _executor;
|
|
55
|
+
|
|
56
|
+
/// 发起支付。
|
|
57
|
+
///
|
|
58
|
+
/// 默认不内置 SDK 实现,避免 Core 或 Helper 强绑定某个支付渠道。
|
|
59
|
+
Future<PaymentResult> pay(PaymentRequest request) {
|
|
60
|
+
final executor = _executor;
|
|
61
|
+
if (executor == null) {
|
|
62
|
+
throw StateError('PaymentExecutor is not configured.');
|
|
63
|
+
}
|
|
64
|
+
return executor(request);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Permission Helper
|
|
2
|
+
|
|
3
|
+
权限 Helper 提供项目级权限申请能力,生成后已包含 `permission_handler` 默认实现。
|
|
4
|
+
|
|
5
|
+
## 生成定位
|
|
6
|
+
|
|
7
|
+
- 落点:`lib/helpers/permission`
|
|
8
|
+
- 边界:不属于 Core,不从 `lib/core/index.dart` 导出
|
|
9
|
+
- 普通 Flutter 依赖:`permission_handler`
|
|
10
|
+
- 最小闭环:`PermissionHelper().request(AppPermission.camera)` 可直接触发权限申请
|
|
11
|
+
- 示例入口:只有选择 `permissionBasic` 时,首页才展示权限示例入口
|
|
12
|
+
|
|
13
|
+
## 平台权限
|
|
14
|
+
|
|
15
|
+
- Android:CLI 会默认写入相机与图片读取权限,落点 `android/app/src/main/AndroidManifest.xml`。
|
|
16
|
+
- iOS:CLI 会默认补 `ios/Runner/Info.plist` 中的 `NSCameraUsageDescription`、`NSPhotoLibraryUsageDescription`,并在 `ios/Podfile` 中启用 `PERMISSION_CAMERA=1`、`PERMISSION_PHOTOS=1`。
|
|
17
|
+
- 修改入口:统一查看 `lib/helpers/README.platform-permissions.md`,按真实业务文案调整权限用途说明。
|
|
18
|
+
- Web / Desktop:按 `permission_handler` 当前平台支持情况决定是否可用。
|
|
19
|
+
|
|
20
|
+
{{#if_harmony}}
|
|
21
|
+
## Harmony 注意
|
|
22
|
+
|
|
23
|
+
当前项目包含 ohos 平台。CLI 会默认尝试在 `ohos/entry/src/main/module.json5` 的 `requestPermissions` 中补 `ohos.permission.CAMERA` 与 `ohos.permission.READ_IMAGEVIDEO`;如你的工程目录不同,请按当前 Harmony Flutter SDK 和鸿蒙应用权限规范核对。
|
|
24
|
+
|
|
25
|
+
鸿蒙插件适配参考:
|
|
26
|
+
|
|
27
|
+
- Flutter packages 适配仓库:https://gitcode.com/openharmony-tpc/flutter_packages
|
|
28
|
+
- Plus plugins 适配仓库:https://gitcode.com/openharmony-sig/flutter_plus_plugins
|
|
29
|
+
- 指导参考:https://gitee.com/openharmony-sig/flutter_plus_plugins/blob/master/GUIDE_DOCUMENT.md
|
|
30
|
+
{{/if_harmony}}
|