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.
Files changed (118) 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 +17766 -3244
  7. package/dist/index.d.cts +783 -99
  8. package/dist/index.d.ts +783 -99
  9. package/dist/index.js +17323 -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/.vscode/settings.json.template +14 -0
  66. package/templates/starter_project/DEVELOPER_GUIDE.md.template +169 -0
  67. package/templates/starter_project/README.md.template +117 -0
  68. package/templates/starter_project/analysis_options.yaml.template +28 -0
  69. package/templates/starter_project/lib/app.dart.template +22 -0
  70. package/templates/starter_project/lib/main.dart.template +34 -0
  71. package/templates/starter_project/lib/pages/splash_page.dart.template +154 -0
  72. package/templates/template_clean/lib/features/home/data/datasources/index.dart +1 -0
  73. package/templates/template_clean/lib/features/home/data/models/index.dart +1 -0
  74. package/templates/template_clean/lib/features/home/domain/index.dart +1 -0
  75. package/templates/template_clean/lib/features/home/presentation/pages/home_page.dart +290 -0
  76. package/templates/template_clean/lib/features/home/presentation/pages/index.dart +2 -0
  77. package/templates/template_clean/lib/features/home/presentation/pages/splash_page.dart +154 -0
  78. package/templates/template_clean/lib/features/home/presentation/viewmodels/home_viewmodel.dart +17 -0
  79. package/templates/template_clean/lib/features/index.dart +2 -0
  80. package/templates/template_clean/lib/features/user/data/datasources/home_feed_service.dart +37 -0
  81. package/templates/template_clean/lib/features/user/data/datasources/index.dart +4 -0
  82. package/templates/template_clean/lib/features/user/data/models/index.dart +3 -0
  83. package/templates/template_clean/lib/features/user/data/models/user.dart +15 -0
  84. package/templates/template_clean/lib/features/user/domain/index.dart +1 -0
  85. package/templates/template_clean/lib/features/user/presentation/pages/index.dart +1 -0
  86. package/templates/template_clean/lib/features/user/presentation/pages/user_list_page.dart +27 -0
  87. package/templates/template_clean/lib/features/user/presentation/viewmodels/user_list_viewmodel.dart +88 -0
  88. package/templates/template_clean/lib/features/user/presentation/widgets/user_item_card.dart +24 -0
  89. package/templates/template_clean/lib/shared/extensions/index.dart +1 -0
  90. package/templates/template_clean/lib/shared/widgets/index.dart +1 -0
  91. package/templates/template_lite/lib/models/index.dart +1 -0
  92. package/templates/template_lite/lib/pages/home_page.dart +290 -0
  93. package/templates/template_lite/lib/pages/index.dart +3 -0
  94. package/templates/template_lite/lib/pages/splash_page.dart +154 -0
  95. package/templates/template_lite/lib/pages/user_list_page.dart +29 -0
  96. package/templates/template_lite/lib/services/home_feed_service.dart +37 -0
  97. package/templates/template_lite/lib/services/index.dart +5 -0
  98. package/templates/template_lite/lib/utils/index.dart +1 -0
  99. package/templates/template_lite/lib/viewmodels/home_viewmodel.dart +34 -0
  100. package/templates/template_lite/lib/viewmodels/index.dart +2 -0
  101. package/templates/template_lite/lib/viewmodels/user_list_viewmodel.dart +103 -0
  102. package/templates/template_lite/lib/widgets/index.dart +1 -0
  103. package/templates/template_lite/lib/widgets/user_item_widget.dart +57 -0
  104. package/templates/template_modular/lib/features/home/index.dart +2 -0
  105. package/templates/template_modular/lib/features/home/models/index.dart +1 -0
  106. package/templates/template_modular/lib/features/home/pages/home_page.dart +290 -0
  107. package/templates/template_modular/lib/features/home/pages/index.dart +2 -0
  108. package/templates/template_modular/lib/features/home/pages/splash_page.dart +154 -0
  109. package/templates/template_modular/lib/features/home/services/index.dart +1 -0
  110. package/templates/template_modular/lib/features/home/viewmodels/home_viewmodel.dart +17 -0
  111. package/templates/template_modular/lib/features/index.dart +2 -0
  112. package/templates/template_modular/lib/features/user/index.dart +6 -0
  113. package/templates/template_modular/lib/features/user/pages/user_list_page.dart +26 -0
  114. package/templates/template_modular/lib/features/user/services/home_feed_service.dart +37 -0
  115. package/templates/template_modular/lib/features/user/viewmodels/user_list_viewmodel.dart +103 -0
  116. package/templates/template_modular/lib/features/user/widgets/user_item_widget.dart +24 -0
  117. package/templates/template_modular/lib/shared/utils/index.dart +1 -0
  118. package/templates/template_modular/lib/shared/widgets/index.dart +1 -0
@@ -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 datasources 导出入口。
@@ -0,0 +1 @@
1
+ // Home data models 导出入口。
@@ -0,0 +1 @@
1
+ // Home domain 导出入口。
@@ -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
+ 'Clean · $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,2 @@
1
+ export 'home_page.dart';
2
+ export 'splash_page.dart';
@@ -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,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}}
@@ -0,0 +1,2 @@
1
+ export 'home/presentation/pages/index.dart';
2
+ export 'user/presentation/pages/index.dart';
@@ -0,0 +1,37 @@
1
+ {{#if_network_example}}
2
+ import '../../../../core/index.dart';
3
+
4
+ class HomeFeedService {
5
+ HomeFeedService({
6
+ AppHttp? http,
7
+ }) : _http = http ?? AppHttp();
8
+
9
+ final AppHttp _http;
10
+
11
+ Future<List<Map<String, dynamic>>> fetchPosts({
12
+ required int page,
13
+ required int pageSize,
14
+ }) async {
15
+ final response = await _http.get<List<Map<String, dynamic>>>(
16
+ '/posts',
17
+ queryParameters: {
18
+ '_page': page,
19
+ '_limit': pageSize,
20
+ },
21
+ showError: false,
22
+ fromJson: (json) {
23
+ final list = json as List;
24
+ return list
25
+ .map((item) => Map<String, dynamic>.from(item as Map))
26
+ .toList();
27
+ },
28
+ );
29
+
30
+ if (response.isSuccess && response.data != null) {
31
+ return response.data!;
32
+ }
33
+
34
+ throw Exception(response.msg);
35
+ }
36
+ }
37
+ {{/if_network_example}}
@@ -0,0 +1,4 @@
1
+ // User datasources 导出入口。
2
+ {{#if_network_example}}
3
+ export 'home_feed_service.dart';
4
+ {{/if_network_example}}
@@ -0,0 +1,3 @@
1
+ {{#if_network_example}}
2
+ export 'user.dart';
3
+ {{/if_network_example}}
@@ -0,0 +1,15 @@
1
+ class User {
2
+ final String name;
3
+
4
+ const User(this.name);
5
+
6
+ {{#if_network_example}}
7
+ factory User.fromNetworkJson(Map<String, dynamic> json) {
8
+ final id = json['id'] as int? ?? 0;
9
+ final title = json['title'] as String? ?? '网络记录 #$id';
10
+ final body = json['body'] as String? ?? '';
11
+
12
+ return User(title.isNotEmpty ? title : body);
13
+ }
14
+ {{/if_network_example}}
15
+ }
@@ -0,0 +1 @@
1
+ // User domain 导出入口。
@@ -0,0 +1 @@
1
+ export 'user_list_page.dart';