flu-cli-core 1.0.5 → 1.1.1
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 +17766 -3244
- package/dist/index.d.cts +783 -99
- package/dist/index.d.ts +783 -99
- package/dist/index.js +17323 -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/.vscode/settings.json.template +14 -0
- package/templates/starter_project/DEVELOPER_GUIDE.md.template +169 -0
- package/templates/starter_project/README.md.template +117 -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,34 @@
|
|
|
1
|
+
{{#if_base_viewmodel}}
|
|
2
|
+
import '../core/index.dart';
|
|
3
|
+
|
|
4
|
+
class HomeViewModel extends BaseViewModel {
|
|
5
|
+
{{else}}
|
|
6
|
+
import 'package:flutter/foundation.dart';
|
|
7
|
+
|
|
8
|
+
class HomeViewModel extends ChangeNotifier {
|
|
9
|
+
{{/if_base_viewmodel}}
|
|
10
|
+
int _counter = 0;
|
|
11
|
+
|
|
12
|
+
int get counter => _counter;
|
|
13
|
+
|
|
14
|
+
{{#if_base_viewmodel}}
|
|
15
|
+
@override
|
|
16
|
+
Future<void> onInit() async {
|
|
17
|
+
await super.onInit();
|
|
18
|
+
setState(ViewState.success);
|
|
19
|
+
}
|
|
20
|
+
{{else}}
|
|
21
|
+
Future<void> onInit() async {}
|
|
22
|
+
{{/if_base_viewmodel}}
|
|
23
|
+
|
|
24
|
+
void increment() {
|
|
25
|
+
_counter++;
|
|
26
|
+
notifyListeners();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void decrement() {
|
|
30
|
+
if (_counter == 0) return;
|
|
31
|
+
_counter--;
|
|
32
|
+
notifyListeners();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import '../core/index.dart';
|
|
2
|
+
{{#if_network_example}}
|
|
3
|
+
import '../services/index.dart';
|
|
4
|
+
{{/if_network_example}}
|
|
5
|
+
|
|
6
|
+
class User {
|
|
7
|
+
final String name;
|
|
8
|
+
|
|
9
|
+
const User(this.name);
|
|
10
|
+
|
|
11
|
+
{{#if_network_example}}
|
|
12
|
+
factory User.fromNetworkJson(Map<String, dynamic> json) {
|
|
13
|
+
final id = json['id'] as int? ?? 0;
|
|
14
|
+
final title = json['title'] as String? ?? '网络记录 #$id';
|
|
15
|
+
final body = json['body'] as String? ?? '';
|
|
16
|
+
|
|
17
|
+
return User(title.isNotEmpty ? title : body);
|
|
18
|
+
}
|
|
19
|
+
{{/if_network_example}}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
enum DemoListMode {
|
|
23
|
+
standard,
|
|
24
|
+
finite,
|
|
25
|
+
empty,
|
|
26
|
+
firstPageError,
|
|
27
|
+
loadMoreError,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class UserListViewModel extends BaseListViewModel<User> {
|
|
31
|
+
{{#if_network_example}}
|
|
32
|
+
UserListViewModel({
|
|
33
|
+
HomeFeedService? feedService,
|
|
34
|
+
}) {
|
|
35
|
+
_feedService = feedService ?? HomeFeedService();
|
|
36
|
+
pageSize = 12;
|
|
37
|
+
}
|
|
38
|
+
{{else}}
|
|
39
|
+
UserListViewModel() {
|
|
40
|
+
pageSize = 12;
|
|
41
|
+
}
|
|
42
|
+
{{/if_network_example}}
|
|
43
|
+
|
|
44
|
+
final DemoListMode mode = _resolveMode();
|
|
45
|
+
{{#if_network_example}}
|
|
46
|
+
late final HomeFeedService _feedService;
|
|
47
|
+
{{/if_network_example}}
|
|
48
|
+
|
|
49
|
+
@override
|
|
50
|
+
Future<List<User>> fetchPage({
|
|
51
|
+
required int page,
|
|
52
|
+
required int pageSize,
|
|
53
|
+
}) async {
|
|
54
|
+
{{#if_network_example}}
|
|
55
|
+
if (!AppConfig.I.useMockData) {
|
|
56
|
+
final posts = await _feedService.fetchPosts(page: page, pageSize: pageSize);
|
|
57
|
+
return posts.map(User.fromNetworkJson).toList();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
{{/if_network_example}}
|
|
61
|
+
await Future<void>.delayed(const Duration(milliseconds: 250));
|
|
62
|
+
|
|
63
|
+
if (mode == DemoListMode.empty && page == 1) {
|
|
64
|
+
return const <User>[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (mode == DemoListMode.firstPageError && page == 1) {
|
|
68
|
+
throw Exception('列表样板加载失败,请点击重试');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (mode == DemoListMode.loadMoreError && page >= 2) {
|
|
72
|
+
throw Exception('分页加载失败,请重试');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (mode == DemoListMode.finite && page >= 3) {
|
|
76
|
+
return List<User>.generate(
|
|
77
|
+
5,
|
|
78
|
+
(index) => User('样板记录 ${(page - 1) * pageSize + index + 1}'),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return List<User>.generate(
|
|
83
|
+
pageSize,
|
|
84
|
+
(index) => User('样板记录 ${(page - 1) * pageSize + index + 1}'),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static DemoListMode _resolveMode() {
|
|
89
|
+
switch (EnvConfig.demoListMode.trim().toLowerCase()) {
|
|
90
|
+
case 'standard':
|
|
91
|
+
return DemoListMode.standard;
|
|
92
|
+
case 'empty':
|
|
93
|
+
return DemoListMode.empty;
|
|
94
|
+
case 'first_page_error':
|
|
95
|
+
return DemoListMode.firstPageError;
|
|
96
|
+
case 'load_more_error':
|
|
97
|
+
return DemoListMode.loadMoreError;
|
|
98
|
+
case 'finite':
|
|
99
|
+
default:
|
|
100
|
+
return DemoListMode.finite;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export 'user_item_widget.dart';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
import '../viewmodels/index.dart';
|
|
4
|
+
|
|
5
|
+
class UserItemWidget extends StatelessWidget {
|
|
6
|
+
final User item;
|
|
7
|
+
|
|
8
|
+
const UserItemWidget({
|
|
9
|
+
super.key,
|
|
10
|
+
required this.item,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
@override
|
|
14
|
+
Widget build(BuildContext context) {
|
|
15
|
+
return Container(
|
|
16
|
+
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
|
17
|
+
padding: const EdgeInsets.all(16),
|
|
18
|
+
decoration: BoxDecoration(
|
|
19
|
+
color: Colors.white,
|
|
20
|
+
borderRadius: BorderRadius.circular(14),
|
|
21
|
+
boxShadow: [
|
|
22
|
+
BoxShadow(
|
|
23
|
+
color: Colors.black.withAlpha(10),
|
|
24
|
+
blurRadius: 10,
|
|
25
|
+
offset: const Offset(0, 3),
|
|
26
|
+
),
|
|
27
|
+
],
|
|
28
|
+
),
|
|
29
|
+
child: Row(
|
|
30
|
+
children: [
|
|
31
|
+
CircleAvatar(
|
|
32
|
+
backgroundColor: const Color(0xFF1B5E20).withAlpha(26),
|
|
33
|
+
child: Text(
|
|
34
|
+
item.name.substring(0, 1),
|
|
35
|
+
style: const TextStyle(
|
|
36
|
+
color: Color(0xFF1B5E20),
|
|
37
|
+
fontWeight: FontWeight.w700,
|
|
38
|
+
),
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
const SizedBox(width: 14),
|
|
42
|
+
Expanded(
|
|
43
|
+
child: Column(
|
|
44
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
45
|
+
children: [
|
|
46
|
+
Text(item.name, style: const TextStyle(fontWeight: FontWeight.w600)),
|
|
47
|
+
const SizedBox(height: 4),
|
|
48
|
+
Text('${item.name.toLowerCase()}@example.com'),
|
|
49
|
+
],
|
|
50
|
+
),
|
|
51
|
+
),
|
|
52
|
+
const Icon(Icons.chevron_right),
|
|
53
|
+
],
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Home 模块模型导出入口。
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
|
|
3
|
+
import '../../../core/index.dart';
|
|
4
|
+
{{#if_base_page}}
|
|
5
|
+
import '../viewmodels/home_viewmodel.dart';
|
|
6
|
+
{{/if_base_page}}
|
|
7
|
+
|
|
8
|
+
{{#if_base_page}}
|
|
9
|
+
class HomePage extends BasePage<HomeViewModel> {
|
|
10
|
+
const HomePage({super.key});
|
|
11
|
+
|
|
12
|
+
@override
|
|
13
|
+
State<HomePage> createState() => _HomePageState();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class _HomePageState extends BasePageState<HomeViewModel, HomePage> {
|
|
17
|
+
@override
|
|
18
|
+
bool get showAppBar => false;
|
|
19
|
+
|
|
20
|
+
@override
|
|
21
|
+
bool get safeAreaTop => false;
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
bool get safeAreaBottom => false;
|
|
25
|
+
|
|
26
|
+
@override
|
|
27
|
+
Color? get backgroundColor => const Color(0xFFF4F7FB);
|
|
28
|
+
|
|
29
|
+
@override
|
|
30
|
+
HomeViewModel createViewModel() => HomeViewModel();
|
|
31
|
+
|
|
32
|
+
@override
|
|
33
|
+
Widget buildContent(BuildContext context) => const _HomePageBody();
|
|
34
|
+
}
|
|
35
|
+
{{else}}
|
|
36
|
+
class HomePage extends StatefulWidget {
|
|
37
|
+
const HomePage({super.key});
|
|
38
|
+
|
|
39
|
+
@override
|
|
40
|
+
State<HomePage> createState() => _HomePageState();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class _HomePageState extends State<HomePage> {
|
|
44
|
+
@override
|
|
45
|
+
Widget build(BuildContext context) {
|
|
46
|
+
return const Scaffold(
|
|
47
|
+
backgroundColor: Color(0xFFF4F7FB),
|
|
48
|
+
body: _HomePageBody(),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
{{/if_base_page}}
|
|
53
|
+
|
|
54
|
+
class _HomePageBody extends StatelessWidget {
|
|
55
|
+
const _HomePageBody();
|
|
56
|
+
|
|
57
|
+
@override
|
|
58
|
+
Widget build(BuildContext context) {
|
|
59
|
+
final config = AppConfig.isInitialized ? AppConfig.I : null;
|
|
60
|
+
final appName = config?.appName ?? '{{projectDisplayName}}';
|
|
61
|
+
final envLabel = (config?.env.name ?? 'dev').toUpperCase();
|
|
62
|
+
|
|
63
|
+
return SafeArea(
|
|
64
|
+
child: CustomScrollView(
|
|
65
|
+
slivers: [
|
|
66
|
+
SliverToBoxAdapter(
|
|
67
|
+
child: Padding(
|
|
68
|
+
padding: const EdgeInsets.fromLTRB(20, 14, 20, 28),
|
|
69
|
+
child: Column(
|
|
70
|
+
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
71
|
+
children: [
|
|
72
|
+
_buildTopBar(appName, envLabel),
|
|
73
|
+
const SizedBox(height: 28),
|
|
74
|
+
_buildSectionTitle('开始使用'),
|
|
75
|
+
const SizedBox(height: 12),
|
|
76
|
+
_buildActionList([
|
|
77
|
+
_buildActionCard(
|
|
78
|
+
icon: Icons.add_box_outlined,
|
|
79
|
+
title: '创建第一个页面',
|
|
80
|
+
description: '使用 flu add page 或 VSCode 右键生成业务页面',
|
|
81
|
+
accentColor: const Color(0xFF2563EB),
|
|
82
|
+
onTap: () => _showCreatePageHint(context),
|
|
83
|
+
),
|
|
84
|
+
_buildActionCard(
|
|
85
|
+
icon: Icons.privacy_tip_outlined,
|
|
86
|
+
title: '协议与隐私',
|
|
87
|
+
description: '查看用户协议、隐私政策和本地合规说明',
|
|
88
|
+
accentColor: const Color(0xFF059669),
|
|
89
|
+
onTap: () => _openAgreementMenu(context),
|
|
90
|
+
),
|
|
91
|
+
]),
|
|
92
|
+
// 示例模块 - 由 CLI 在选择示例时动态注入
|
|
93
|
+
],
|
|
94
|
+
),
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
],
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Widget _buildTopBar(String appName, String envLabel) {
|
|
103
|
+
return Row(
|
|
104
|
+
children: [
|
|
105
|
+
Container(
|
|
106
|
+
width: 42,
|
|
107
|
+
height: 42,
|
|
108
|
+
decoration: BoxDecoration(
|
|
109
|
+
color: const Color(0xFF111827),
|
|
110
|
+
borderRadius: BorderRadius.circular(13),
|
|
111
|
+
),
|
|
112
|
+
child: const Icon(
|
|
113
|
+
Icons.dashboard_customize_outlined,
|
|
114
|
+
color: Colors.white,
|
|
115
|
+
size: 21,
|
|
116
|
+
),
|
|
117
|
+
),
|
|
118
|
+
const SizedBox(width: 12),
|
|
119
|
+
Expanded(
|
|
120
|
+
child: Column(
|
|
121
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
122
|
+
children: [
|
|
123
|
+
Text(
|
|
124
|
+
appName,
|
|
125
|
+
maxLines: 1,
|
|
126
|
+
overflow: TextOverflow.ellipsis,
|
|
127
|
+
style: const TextStyle(
|
|
128
|
+
color: Color(0xFF111827),
|
|
129
|
+
fontSize: 19,
|
|
130
|
+
fontWeight: FontWeight.w800,
|
|
131
|
+
),
|
|
132
|
+
),
|
|
133
|
+
const SizedBox(height: 2),
|
|
134
|
+
Text(
|
|
135
|
+
'Modular · $envLabel',
|
|
136
|
+
style: const TextStyle(
|
|
137
|
+
color: Color(0xFF64748B),
|
|
138
|
+
fontSize: 12,
|
|
139
|
+
fontWeight: FontWeight.w600,
|
|
140
|
+
),
|
|
141
|
+
),
|
|
142
|
+
],
|
|
143
|
+
),
|
|
144
|
+
),
|
|
145
|
+
],
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
Widget _buildSectionTitle(String title) {
|
|
150
|
+
return Text(
|
|
151
|
+
title,
|
|
152
|
+
style: const TextStyle(
|
|
153
|
+
color: Color(0xFF111827),
|
|
154
|
+
fontSize: 18,
|
|
155
|
+
fontWeight: FontWeight.w800,
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
Widget _buildActionList(List<Widget> actions) {
|
|
161
|
+
return Column(
|
|
162
|
+
children: [
|
|
163
|
+
for (var i = 0; i < actions.length; i++) ...[
|
|
164
|
+
if (i > 0) const SizedBox(height: 10),
|
|
165
|
+
actions[i],
|
|
166
|
+
],
|
|
167
|
+
],
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
Widget _buildActionCard({
|
|
172
|
+
required IconData icon,
|
|
173
|
+
required String title,
|
|
174
|
+
required String description,
|
|
175
|
+
required Color accentColor,
|
|
176
|
+
required VoidCallback onTap,
|
|
177
|
+
}) {
|
|
178
|
+
return Material(
|
|
179
|
+
color: Colors.white,
|
|
180
|
+
borderRadius: BorderRadius.circular(16),
|
|
181
|
+
child: InkWell(
|
|
182
|
+
onTap: onTap,
|
|
183
|
+
borderRadius: BorderRadius.circular(16),
|
|
184
|
+
child: Container(
|
|
185
|
+
padding: const EdgeInsets.all(16),
|
|
186
|
+
decoration: BoxDecoration(
|
|
187
|
+
border: Border.all(color: const Color(0xFFE2E8F0)),
|
|
188
|
+
borderRadius: BorderRadius.circular(16),
|
|
189
|
+
),
|
|
190
|
+
child: Row(
|
|
191
|
+
children: [
|
|
192
|
+
Container(
|
|
193
|
+
width: 42,
|
|
194
|
+
height: 42,
|
|
195
|
+
decoration: BoxDecoration(
|
|
196
|
+
color: accentColor.withAlpha(20),
|
|
197
|
+
borderRadius: BorderRadius.circular(12),
|
|
198
|
+
),
|
|
199
|
+
child: Icon(icon, color: accentColor, size: 21),
|
|
200
|
+
),
|
|
201
|
+
const SizedBox(width: 12),
|
|
202
|
+
Expanded(
|
|
203
|
+
child: Column(
|
|
204
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
205
|
+
children: [
|
|
206
|
+
Text(
|
|
207
|
+
title,
|
|
208
|
+
maxLines: 1,
|
|
209
|
+
overflow: TextOverflow.ellipsis,
|
|
210
|
+
style: const TextStyle(
|
|
211
|
+
color: Color(0xFF111827),
|
|
212
|
+
fontSize: 15,
|
|
213
|
+
fontWeight: FontWeight.w800,
|
|
214
|
+
),
|
|
215
|
+
),
|
|
216
|
+
const SizedBox(height: 5),
|
|
217
|
+
Text(
|
|
218
|
+
description,
|
|
219
|
+
maxLines: 2,
|
|
220
|
+
overflow: TextOverflow.ellipsis,
|
|
221
|
+
style: const TextStyle(
|
|
222
|
+
color: Color(0xFF64748B),
|
|
223
|
+
fontSize: 12,
|
|
224
|
+
height: 1.35,
|
|
225
|
+
),
|
|
226
|
+
),
|
|
227
|
+
],
|
|
228
|
+
),
|
|
229
|
+
),
|
|
230
|
+
const SizedBox(width: 8),
|
|
231
|
+
const Icon(
|
|
232
|
+
Icons.chevron_right,
|
|
233
|
+
color: Color(0xFF94A3B8),
|
|
234
|
+
size: 20,
|
|
235
|
+
),
|
|
236
|
+
],
|
|
237
|
+
),
|
|
238
|
+
),
|
|
239
|
+
),
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
void _showCreatePageHint(BuildContext context) {
|
|
244
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
245
|
+
const SnackBar(
|
|
246
|
+
content: Text('在终端运行 flu add page,或在 VSCode 资源管理器右键生成页面。'),
|
|
247
|
+
),
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
void _openAgreementMenu(BuildContext context) {
|
|
252
|
+
showModalBottomSheet<void>(
|
|
253
|
+
context: context,
|
|
254
|
+
showDragHandle: true,
|
|
255
|
+
backgroundColor: Colors.white,
|
|
256
|
+
builder: (sheetContext) {
|
|
257
|
+
return SafeArea(
|
|
258
|
+
child: Padding(
|
|
259
|
+
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
|
260
|
+
child: Column(
|
|
261
|
+
mainAxisSize: MainAxisSize.min,
|
|
262
|
+
children: [
|
|
263
|
+
ListTile(
|
|
264
|
+
leading: const Icon(Icons.assignment_outlined),
|
|
265
|
+
title: const Text('用户协议'),
|
|
266
|
+
onTap: () {
|
|
267
|
+
Navigator.of(sheetContext).pop();
|
|
268
|
+
_openAgreement(AgreementDocumentType.userAgreement);
|
|
269
|
+
},
|
|
270
|
+
),
|
|
271
|
+
ListTile(
|
|
272
|
+
leading: const Icon(Icons.privacy_tip_outlined),
|
|
273
|
+
title: const Text('隐私政策'),
|
|
274
|
+
onTap: () {
|
|
275
|
+
Navigator.of(sheetContext).pop();
|
|
276
|
+
_openAgreement(AgreementDocumentType.privacyPolicy);
|
|
277
|
+
},
|
|
278
|
+
),
|
|
279
|
+
],
|
|
280
|
+
),
|
|
281
|
+
),
|
|
282
|
+
);
|
|
283
|
+
},
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
void _openAgreement(AgreementDocumentType type) {
|
|
288
|
+
NavigatorUtil.push(AgreementDocumentPage(type: type));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import 'package:flutter/foundation.dart';
|
|
2
|
+
import 'package:flutter/material.dart';
|
|
3
|
+
import 'package:flutter/services.dart';
|
|
4
|
+
|
|
5
|
+
import '../../../core/index.dart';
|
|
6
|
+
|
|
7
|
+
class SplashPage extends StatefulWidget {
|
|
8
|
+
const SplashPage({super.key});
|
|
9
|
+
|
|
10
|
+
@override
|
|
11
|
+
State<SplashPage> createState() => _SplashPageState();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class _SplashPageState extends State<SplashPage>
|
|
15
|
+
with SingleTickerProviderStateMixin {
|
|
16
|
+
late final AnimationController _controller;
|
|
17
|
+
late final Animation<double> _opacity;
|
|
18
|
+
late final Animation<double> _scale;
|
|
19
|
+
bool _blocked = false;
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
void initState() {
|
|
23
|
+
super.initState();
|
|
24
|
+
_controller = AnimationController(
|
|
25
|
+
vsync: this,
|
|
26
|
+
duration: const Duration(milliseconds: 820),
|
|
27
|
+
);
|
|
28
|
+
_opacity = CurvedAnimation(
|
|
29
|
+
parent: _controller,
|
|
30
|
+
curve: Curves.easeOutCubic,
|
|
31
|
+
);
|
|
32
|
+
_scale = Tween<double>(begin: 0.92, end: 1.0).animate(
|
|
33
|
+
CurvedAnimation(
|
|
34
|
+
parent: _controller,
|
|
35
|
+
curve: Curves.easeOutBack,
|
|
36
|
+
),
|
|
37
|
+
);
|
|
38
|
+
_controller.forward();
|
|
39
|
+
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
40
|
+
_checkPrivacy();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@override
|
|
45
|
+
void dispose() {
|
|
46
|
+
_controller.dispose();
|
|
47
|
+
super.dispose();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Future<void> _checkPrivacy() async {
|
|
51
|
+
if (!EnvConfig.enableAgreementDialog || AppConfig.I.hasAcceptedAgreement) {
|
|
52
|
+
_goHome();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!mounted) return;
|
|
57
|
+
final agreed = await PrivacyDialog.show(context);
|
|
58
|
+
if (!mounted) return;
|
|
59
|
+
|
|
60
|
+
if (agreed == true) {
|
|
61
|
+
await AppConfig.acceptCurrentAgreement();
|
|
62
|
+
_goHome();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_handleAgreementRejected();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
void _goHome() {
|
|
70
|
+
if (!mounted) return;
|
|
71
|
+
NavigatorUtil.pushNamed(AppRoutes.home, clearStack: true);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
void _handleAgreementRejected() {
|
|
75
|
+
if (kIsWeb || defaultTargetPlatform == TargetPlatform.iOS) {
|
|
76
|
+
setState(() {
|
|
77
|
+
_blocked = true;
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
SystemNavigator.pop();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@override
|
|
85
|
+
Widget build(BuildContext context) {
|
|
86
|
+
final colors = AppColorConfig.I;
|
|
87
|
+
return Scaffold(
|
|
88
|
+
backgroundColor: colors.background,
|
|
89
|
+
body: SafeArea(
|
|
90
|
+
child: Center(
|
|
91
|
+
child: FadeTransition(
|
|
92
|
+
opacity: _opacity,
|
|
93
|
+
child: ScaleTransition(
|
|
94
|
+
scale: _scale,
|
|
95
|
+
child: Column(
|
|
96
|
+
mainAxisSize: MainAxisSize.min,
|
|
97
|
+
children: [
|
|
98
|
+
Container(
|
|
99
|
+
width: 84,
|
|
100
|
+
height: 84,
|
|
101
|
+
decoration: BoxDecoration(
|
|
102
|
+
gradient: LinearGradient(
|
|
103
|
+
begin: Alignment.topLeft,
|
|
104
|
+
end: Alignment.bottomRight,
|
|
105
|
+
colors: [
|
|
106
|
+
colors.primary,
|
|
107
|
+
colors.primary.withBlue(212),
|
|
108
|
+
],
|
|
109
|
+
),
|
|
110
|
+
borderRadius: BorderRadius.circular(26),
|
|
111
|
+
boxShadow: [
|
|
112
|
+
BoxShadow(
|
|
113
|
+
color: colors.primary.withAlpha(45),
|
|
114
|
+
blurRadius: 24,
|
|
115
|
+
offset: const Offset(0, 12),
|
|
116
|
+
),
|
|
117
|
+
],
|
|
118
|
+
),
|
|
119
|
+
child: const Icon(
|
|
120
|
+
Icons.blur_on_rounded,
|
|
121
|
+
color: Colors.white,
|
|
122
|
+
size: 38,
|
|
123
|
+
),
|
|
124
|
+
),
|
|
125
|
+
const SizedBox(height: 22),
|
|
126
|
+
Text(
|
|
127
|
+
EnvConfig.appName,
|
|
128
|
+
style: AppTextStyleConfig.I.titleLarge(
|
|
129
|
+
fontWeight: FontWeight.w800,
|
|
130
|
+
),
|
|
131
|
+
),
|
|
132
|
+
const SizedBox(height: 8),
|
|
133
|
+
Text(
|
|
134
|
+
_blocked ? '需要同意协议后继续使用' : '正在准备首页入口...',
|
|
135
|
+
style: AppTextStyleConfig.I.bodySmall(
|
|
136
|
+
color: colors.titleSecondary,
|
|
137
|
+
),
|
|
138
|
+
),
|
|
139
|
+
if (_blocked) ...[
|
|
140
|
+
const SizedBox(height: 18),
|
|
141
|
+
FilledButton(
|
|
142
|
+
onPressed: _checkPrivacy,
|
|
143
|
+
child: const Text('重新阅读并同意'),
|
|
144
|
+
),
|
|
145
|
+
],
|
|
146
|
+
],
|
|
147
|
+
),
|
|
148
|
+
),
|
|
149
|
+
),
|
|
150
|
+
),
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Home 模块服务导出入口。
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{{#if_base_viewmodel}}
|
|
2
|
+
import '../../../core/index.dart';
|
|
3
|
+
|
|
4
|
+
class HomeViewModel extends BaseViewModel {
|
|
5
|
+
@override
|
|
6
|
+
Future<void> onInit() async {
|
|
7
|
+
await super.onInit();
|
|
8
|
+
setState(ViewState.success);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
{{else}}
|
|
12
|
+
import 'package:flutter/foundation.dart';
|
|
13
|
+
|
|
14
|
+
class HomeViewModel extends ChangeNotifier {
|
|
15
|
+
Future<void> onInit() async {}
|
|
16
|
+
}
|
|
17
|
+
{{/if_base_viewmodel}}
|