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,22 @@
|
|
|
1
|
+
import 'package:flutter/material.dart';
|
|
2
|
+
import 'core/index.dart';
|
|
3
|
+
|
|
4
|
+
class App extends StatelessWidget {
|
|
5
|
+
const App({super.key});
|
|
6
|
+
|
|
7
|
+
@override
|
|
8
|
+
Widget build(BuildContext context) {
|
|
9
|
+
return MaterialApp(
|
|
10
|
+
navigatorKey: NavigatorUtil.navigatorKey,
|
|
11
|
+
scaffoldMessengerKey: NavigatorUtil.scaffoldMessengerKey,
|
|
12
|
+
title: AppConstants.appName,
|
|
13
|
+
theme: AppTheme.light(),
|
|
14
|
+
darkTheme: AppTheme.dark(),
|
|
15
|
+
themeMode: ThemeMode.system,
|
|
16
|
+
initialRoute:
|
|
17
|
+
AppConfig.I.shouldShowSplash ? AppRoutes.splash : AppRoutes.home,
|
|
18
|
+
routes: AppRoutes.routes,
|
|
19
|
+
debugShowCheckedModeBanner: false,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
|
|
3
|
+
import 'package:flutter/foundation.dart';
|
|
4
|
+
import 'package:flutter/material.dart';
|
|
5
|
+
|
|
6
|
+
import 'app.dart';
|
|
7
|
+
import 'core/index.dart';
|
|
8
|
+
|
|
9
|
+
void main() {
|
|
10
|
+
runZonedGuarded(
|
|
11
|
+
() async => _bootstrap(),
|
|
12
|
+
(error, stackTrace) {
|
|
13
|
+
if (kDebugMode) {
|
|
14
|
+
debugPrint(
|
|
15
|
+
FlutterErrorDetails(exception: error, stack: stackTrace).toString(),
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
Future<void> _bootstrap() async {
|
|
23
|
+
WidgetsFlutterBinding.ensureInitialized();
|
|
24
|
+
FlutterError.onError = (details) {
|
|
25
|
+
FlutterError.presentError(details);
|
|
26
|
+
Zone.current.handleUncaughtError(
|
|
27
|
+
details.exception,
|
|
28
|
+
details.stack ?? StackTrace.current,
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
await AppInitializer.init();
|
|
33
|
+
runApp(const App());
|
|
34
|
+
}
|
|
@@ -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,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
|
+
}
|
package/templates/template_clean/lib/features/home/presentation/viewmodels/home_viewmodel.dart
ADDED
|
@@ -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}}
|