flu-cli 2.0.3 → 2.0.4

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.
@@ -1,174 +0,0 @@
1
- import 'package:flutter/material.dart';
2
-
3
- import 'base_list_viewmodel.dart';
4
- import 'base_page.dart';
5
- import 'theme/status_views_theme.dart';
6
-
7
- /// 列表页面基类:在 BasePage 基础上扩展列表体与触底加载能力
8
- abstract class BaseListPage<T, VM extends BaseListViewModel<T>>
9
- extends BasePage<VM> {
10
- /// 构造函数
11
- const BaseListPage({super.key});
12
-
13
- /// 子类必须实现:创建 ViewModel 实例
14
- VM createListViewModel();
15
-
16
- /// 渲染单个条目
17
- Widget buildItem(BuildContext context, VM vm, T item, int index);
18
-
19
- /// 可选:头部、尾部、状态页覆盖(函数级注释)
20
-
21
- /// 构建成功态的列表体(函数级注释)
22
- /// 返回 null 使用默认 ListView.builder;可自定义 SliverList/GridView 等容器
23
- Widget? buildListWidget(BuildContext context, VM vm) => null;
24
- Widget? buildHeader(BuildContext context, VM vm) => null;
25
- Widget? buildFooter(BuildContext context, VM vm) => null;
26
- Widget? buildLoading(BuildContext context, VM vm) => null;
27
- Widget? buildError(BuildContext context, VM vm, String? msg) => null;
28
- Widget? buildEmpty(BuildContext context, VM vm) => null;
29
-
30
- /// 可选:替换底部“加载更多”视图
31
- Widget? loadMoreFooterBuilder(
32
- BuildContext context,
33
- VM vm,
34
- bool hasMore,
35
- bool isLoadingMore,
36
- ) =>
37
- null;
38
-
39
- /// 刷新容器包裹(用于接入第三方刷新库)
40
- Widget refreshWrapperBuilder(BuildContext context, Widget child, VM vm) =>
41
- child;
42
-
43
- /// 列表体外层包裹(函数级注释)
44
- /// 用于添加 Scrollbar、NotificationListener、吸顶 Header 等额外结构
45
- Widget listWrapperBuilder(BuildContext context, Widget list, VM vm) => list;
46
-
47
- /// 提供自定义 ScrollController(函数级注释)
48
- /// 返回 null 使用内部控制器;非空时由外部管理其生命周期
49
- ScrollController? provideScrollController(BuildContext context, VM vm) =>
50
- null;
51
-
52
- /// 滚动事件回调(函数级注释)
53
- /// 可用于自定义触底策略或滚动埋点
54
- void onScroll(BuildContext context, ScrollPosition position, VM vm) {}
55
-
56
- /// 是否启用自动触底加载
57
- bool get enableAutoLoadMore => true;
58
-
59
- /// 触底阈值
60
- double get loadMoreThreshold => 100;
61
-
62
- /// 是否启用下拉刷新(透传到 BasePageState)
63
- bool get enableRefresh => false;
64
-
65
- @override
66
- State<BaseListPage<T, VM>> createState() => _BaseListPageState<T, VM>();
67
- }
68
-
69
- class _BaseListPageState<T, VM extends BaseListViewModel<T>>
70
- extends BasePageState<VM, BaseListPage<T, VM>> {
71
- late final ScrollController _controller =
72
- widget.provideScrollController(context, viewModel) ?? ScrollController();
73
-
74
- /// 创建 ViewModel(使用注入的实例)
75
- @override
76
- VM createViewModel() => widget.createListViewModel();
77
-
78
- /// 页面是否为空:依据列表数据判断
79
- @override
80
- bool get isEmptyContent => viewModel.items.isEmpty;
81
-
82
- @override
83
- void initState() {
84
- super.initState();
85
- _controller.addListener(_onScroll);
86
- }
87
-
88
- @override
89
- void dispose() {
90
- _controller.removeListener(_onScroll);
91
- _controller.dispose();
92
- super.dispose();
93
- }
94
-
95
- /// 触底检测:接近底部时尝试加载更多
96
- void _onScroll() {
97
- widget.onScroll(context, _controller.position, viewModel);
98
- if (!widget.enableAutoLoadMore) return;
99
- if (!_controller.hasClients) return;
100
- final max = _controller.position.maxScrollExtent;
101
- final offset = _controller.offset;
102
- if (offset >= max - widget.loadMoreThreshold) {
103
- viewModel.loadMore();
104
- }
105
- }
106
-
107
- /// 构建成功态内容:列表 + 底部 Footer
108
- @override
109
- Widget buildContent(BuildContext context) {
110
- Widget body = _buildListWidget();
111
- if (widget.enableRefresh) {
112
- body = widget.refreshWrapperBuilder(context, body, viewModel);
113
- }
114
- return body;
115
- }
116
-
117
- /// 构建列表视图:支持自定义或默认实现
118
- Widget _buildListWidget() {
119
- final vm = viewModel;
120
- final theme = Theme.of(context).extension<StatusViewsTheme>();
121
- final list = widget.buildListWidget(context, vm) ??
122
- ListView.builder(
123
- controller: _controller,
124
- itemCount: vm.items.length + 1,
125
- itemBuilder: (ctx, i) {
126
- if (i == 0) {
127
- final header = widget.buildHeader(ctx, vm);
128
- if (header != null) return header;
129
- }
130
- final last = i == vm.items.length;
131
- if (last) {
132
- final localFooter = widget.buildFooter(ctx, vm);
133
- if (localFooter != null) return localFooter;
134
-
135
- if (vm.loadMoreFailed) {
136
- return Padding(
137
- padding: const EdgeInsets.all(16),
138
- child: Column(
139
- mainAxisSize: MainAxisSize.min,
140
- children: [
141
- Text(
142
- vm.loadMoreError?.toString() ?? '加载更多失败',
143
- style: const TextStyle(color: Colors.red),
144
- ),
145
- const SizedBox(height: 8),
146
- ElevatedButton(
147
- onPressed: vm.loadMore,
148
- child: const Text('重试'),
149
- ),
150
- ],
151
- ),
152
- );
153
- }
154
-
155
- final globalFooter =
156
- theme?.loadMoreBuilder?.call(ctx, vm.hasMore);
157
- if (globalFooter != null) return globalFooter;
158
-
159
- return Padding(
160
- padding: const EdgeInsets.all(16),
161
- child: Center(
162
- child: vm.hasMore
163
- ? const CircularProgressIndicator()
164
- : const Text('没有更多了'),
165
- ),
166
- );
167
- }
168
- final item = vm.items[i];
169
- return widget.buildItem(ctx, vm, item, i);
170
- },
171
- );
172
- return widget.listWrapperBuilder(context, list, vm);
173
- }
174
- }
@@ -1,134 +0,0 @@
1
- import 'base_viewmodel.dart';
2
-
3
- /// 列表基础 ViewModel(轻量版)
4
- ///
5
- /// - 提供分页/刷新/加载更多的最小实现
6
- abstract class BaseListViewModel<T> extends BaseViewModel {
7
- final List<T> items = <T>[];
8
- int page = 1;
9
- int pageSize = 20;
10
- bool hasMore = true;
11
- bool isLoadingMore = false;
12
- bool loadMoreFailed = false;
13
- Object? loadMoreError;
14
-
15
- /// 子类必须实现:拉取一页数据(函数级注释)
16
- Future<List<T>> fetchPage({required int page, required int pageSize});
17
-
18
- /// 初始化加载(函数级注释)
19
- @override
20
- Future<void> onInit() async {
21
- super.onInit();
22
- await loadData();
23
- }
24
-
25
- /// 首屏加载(函数级注释)
26
- Future<void> loadData() async {
27
- setState(ViewState.loading);
28
- page = 1;
29
- hasMore = true;
30
- try {
31
- final list = await fetchPage(page: page, pageSize: pageSize);
32
- items
33
- ..clear()
34
- ..addAll(list);
35
- _configHasMore(list);
36
- setState(ViewState.success);
37
- } catch (e) {
38
- setState(ViewState.error, error: e.toString());
39
- }
40
- }
41
-
42
- /// 下拉刷新(函数级注释)
43
- @override
44
- Future<void> refreshData() async {
45
- if (isRefreshing) return;
46
- setRefreshing(true);
47
- final old = state;
48
- try {
49
- page = 1;
50
- hasMore = true;
51
- final list = await fetchPage(page: page, pageSize: pageSize);
52
- items
53
- ..clear()
54
- ..addAll(list);
55
- _configHasMore(list);
56
- setState(ViewState.success);
57
- } catch (_) {
58
- // 刷新失败回退原状态
59
- setState(old);
60
- } finally {
61
- setRefreshing(false);
62
- }
63
- }
64
-
65
- /// 加载更多(函数级注释)
66
- Future<void> loadMore() async {
67
- if (!hasMore || isLoadingMore) return;
68
- isLoadingMore = true;
69
- loadMoreFailed = false;
70
- loadMoreError = null;
71
- try {
72
- final next = page + 1;
73
- final list = await fetchPage(page: next, pageSize: pageSize);
74
- if (list.isNotEmpty) {
75
- items.addAll(list);
76
- page = next;
77
- }
78
- _configHasMore(list);
79
- } catch (e) {
80
- loadMoreFailed = true;
81
- loadMoreError = e;
82
- } finally {
83
- isLoadingMore = false;
84
- notifyDataChange();
85
- }
86
- }
87
-
88
- /// 重置分页(函数级注释)
89
- void resetPaging({int initialPage = 1, int pageSize = 20}) {
90
- page = initialPage;
91
- this.pageSize = pageSize;
92
- hasMore = true;
93
- items.clear();
94
- loadMoreFailed = false;
95
- loadMoreError = null;
96
- notifyListeners();
97
- setState(ViewState.idle);
98
- }
99
-
100
- /// 计算是否还有更多(函数级注释)
101
- void _configHasMore(List<T> data) {
102
- hasMore = data.length >= pageSize;
103
- loadMoreFailed = false;
104
- loadMoreError = null;
105
- }
106
-
107
- /// 清空列表
108
- void clearItems() {
109
- items.clear();
110
- page = 1;
111
- hasMore = true;
112
- notifyListeners();
113
- }
114
-
115
- /// 数据操作:添加(函数级注释)
116
- void addItem(T item) {
117
- items.add(item);
118
- notifyListeners();
119
- }
120
-
121
- /// 数据操作:移除(函数级注释)
122
- void removeItem(T item) {
123
- items.remove(item);
124
- notifyListeners();
125
- }
126
-
127
- /// 数据操作:更新(函数级注释)
128
- void updateItem(int index, T item) {
129
- if (index >= 0 && index < items.length) {
130
- items[index] = item;
131
- notifyListeners();
132
- }
133
- }
134
- }
@@ -1,251 +0,0 @@
1
- import 'package:flutter/material.dart';
2
-
3
- import 'base_viewmodel.dart';
4
- import 'theme/status_views_theme.dart';
5
-
6
- /// Page 基类
7
- ///
8
- /// 提供统一的页面结构和状态处理
9
- ///
10
- /// 示例:
11
- /// ```dart
12
- /// class HomePage extends BasePage<HomeViewModel> {
13
- /// const HomePage({super.key});
14
- ///
15
- /// @override
16
- /// State<HomePage> createState() => _HomePageState();
17
- /// }
18
- ///
19
- /// class _HomePageState extends BasePageState<HomeViewModel, HomePage> {
20
- /// @override
21
- /// HomeViewModel createViewModel() => HomeViewModel();
22
- ///
23
- /// @override
24
- /// String get title => '首页';
25
- ///
26
- /// @override
27
- /// Widget buildContent(BuildContext context) {
28
- /// return Center(
29
- /// child: Text('Counter: ${viewModel.counter}'),
30
- /// );
31
- /// }
32
- /// }
33
- /// ```
34
- abstract class BasePage<VM extends BaseViewModel> extends StatefulWidget {
35
- const BasePage({super.key});
36
-
37
- /// 是否显示 AppBar(默认 true)
38
- bool get showAppBar => true;
39
-
40
- /// AppBar 标题(默认空串)
41
- String get title => '';
42
-
43
- /// AppBar 操作按钮(默认 null)
44
- List<Widget>? get actions => null;
45
-
46
- /// 是否显示返回按钮(默认自动判断)
47
- bool? get showBackButton => null;
48
-
49
- /// 背景颜色(默认 null)
50
- Color? get backgroundColor => null;
51
-
52
- /// 是否启用 SafeArea(默认 true)
53
- bool get useSafeArea => true;
54
-
55
- @override
56
- State<BasePage<VM>> createState();
57
- }
58
-
59
- /// Page State 基类
60
- abstract class BasePageState<VM extends BaseViewModel, T extends BasePage<VM>>
61
- extends State<T> {
62
- /// ViewModel 实例
63
- late final VM viewModel;
64
-
65
- // ==================== 子类需要实现的方法 ====================
66
-
67
- /// 创建 ViewModel
68
- VM createViewModel();
69
-
70
- /// 构建内容区域
71
- Widget buildContent(BuildContext context);
72
-
73
- // ==================== 可选重写的方法 ====================
74
-
75
- /// 是否显示 AppBar(透传自 Widget 层)
76
- bool get showAppBar => widget.showAppBar;
77
-
78
- /// AppBar 标题(透传自 Widget 层)
79
- String get title => widget.title;
80
-
81
- /// AppBar 操作按钮(透传自 Widget 层)
82
- List<Widget>? get actions => widget.actions;
83
-
84
- /// 是否显示返回按钮(透传自 Widget 层)
85
- bool? get showBackButton => widget.showBackButton;
86
-
87
- /// 背景颜色(透传自 Widget 层)
88
- Color? get backgroundColor => widget.backgroundColor;
89
-
90
- /// 是否安全区域(透传自 Widget 层)
91
- bool get useSafeArea => widget.useSafeArea;
92
-
93
- // ==================== 生命周期 ====================
94
-
95
- @override
96
- void initState() {
97
- super.initState();
98
- viewModel = createViewModel();
99
- _setupListener();
100
- viewModel.onInit();
101
- }
102
-
103
- /// 设置监听器
104
- void _setupListener() {
105
- // 所有状态管理器都使用统一的 addListener 方式
106
- viewModel.addListener(_onViewModelChanged);
107
- }
108
-
109
- @override
110
- void dispose() {
111
- viewModel.removeListener(_onViewModelChanged);
112
- viewModel.dispose();
113
- super.dispose();
114
- }
115
-
116
- /// ViewModel 变化回调
117
- void _onViewModelChanged() {
118
- if (mounted) {
119
- setState(() {});
120
- }
121
- }
122
-
123
- // ==================== UI 构建 ====================
124
-
125
- @override
126
- Widget build(BuildContext context) {
127
- Widget body = _buildBody();
128
-
129
- if (useSafeArea) {
130
- body = SafeArea(child: body);
131
- }
132
-
133
- return Scaffold(
134
- appBar: showAppBar ? _buildAppBar() : null,
135
- backgroundColor: backgroundColor,
136
- body: body,
137
- );
138
- }
139
-
140
- /// 构建 AppBar
141
- PreferredSizeWidget? _buildAppBar() {
142
- return AppBar(
143
- title: Text(title),
144
- actions: actions,
145
- automaticallyImplyLeading: showBackButton ?? true,
146
- );
147
- }
148
-
149
- /// 构建 Body
150
- Widget _buildBody() {
151
- final theme = Theme.of(context).extension<StatusViewsTheme>();
152
-
153
- if (viewModel.isLoading) {
154
- final global = theme?.loadingBuilder?.call(context);
155
- return global ?? buildLoading();
156
- }
157
-
158
- if (viewModel.isError) {
159
- final global = theme?.errorBuilder?.call(context, viewModel.errorMessage);
160
- return global ?? buildError();
161
- }
162
-
163
- if (isEmptyContent) {
164
- final global = theme?.emptyBuilder?.call(context);
165
- return global ?? buildEmpty();
166
- }
167
-
168
- return buildContent(context);
169
- }
170
-
171
- // ==================== 可重写的 UI 组件 ====================
172
-
173
- /// 构建 Loading 视图
174
- Widget buildLoading() {
175
- return const Center(
176
- child: CircularProgressIndicator(),
177
- );
178
- }
179
-
180
- /// 构建错误视图
181
- Widget buildError() {
182
- return Center(
183
- child: Column(
184
- mainAxisAlignment: MainAxisAlignment.center,
185
- children: [
186
- const Icon(
187
- Icons.error_outline,
188
- size: 64,
189
- color: Colors.red,
190
- ),
191
- const SizedBox(height: 16),
192
- Text(
193
- viewModel.errorMessage ?? '加载失败',
194
- style: const TextStyle(fontSize: 16),
195
- ),
196
- const SizedBox(height: 24),
197
- ElevatedButton.icon(
198
- onPressed: viewModel.refreshData,
199
- icon: const Icon(Icons.refresh),
200
- label: const Text('重试'),
201
- ),
202
- ],
203
- ),
204
- );
205
- }
206
-
207
- /// 构建空视图
208
- Widget buildEmpty() {
209
- return const Center(
210
- child: Text('暂无数据'),
211
- );
212
- }
213
-
214
- /// 页面是否为空(默认 false,子类可重写)
215
- bool get isEmptyContent => false;
216
-
217
- /// 显示 SnackBar
218
- void showSnackBar(String message, {Duration? duration}) {
219
- if (!mounted) return;
220
- ScaffoldMessenger.of(context).showSnackBar(
221
- SnackBar(
222
- content: Text(message),
223
- duration: duration ?? const Duration(seconds: 2),
224
- ),
225
- );
226
- }
227
-
228
- /// 显示 Loading Dialog
229
- void showLoadingDialog({String? message}) {
230
- showDialog(
231
- context: context,
232
- barrierDismissible: false,
233
- builder: (context) => AlertDialog(
234
- content: Row(
235
- children: [
236
- const CircularProgressIndicator(),
237
- const SizedBox(width: 16),
238
- Text(message ?? '加载中...'),
239
- ],
240
- ),
241
- ),
242
- );
243
- }
244
-
245
- /// 隐藏 Loading Dialog
246
- void hideLoadingDialog() {
247
- if (mounted) {
248
- Navigator.of(context).pop();
249
- }
250
- }
251
- }
@@ -1,77 +0,0 @@
1
- import 'package:flutter/foundation.dart';
2
-
3
- /// 页面状态定义
4
- enum ViewState { idle, loading, success, error }
5
-
6
- class BaseViewModel extends ChangeNotifier {
7
- ViewState _state = ViewState.idle;
8
- String? _errorMessage;
9
- bool _isRefreshing = false;
10
- // ==================== Getters ====================
11
- /// 当前视图状态
12
- ViewState get state => _state;
13
-
14
- /// 错误信息
15
- String? get errorMessage => _errorMessage;
16
-
17
- /// 是否正在加载
18
- bool get isLoading => _state == ViewState.loading;
19
-
20
- /// 是否加载失败
21
- bool get isError => _state == ViewState.error;
22
-
23
- /// 是否加载成功
24
- bool get isSuccess => _state == ViewState.success;
25
-
26
- /// 是否空闲状态
27
- bool get isIdle => _state == ViewState.idle;
28
-
29
- /// 是否正在刷新
30
-
31
- bool get isRefreshing => _isRefreshing;
32
- // ==================== 状态设置 ====================
33
- /// 设置视图状态
34
- ///
35
- /// [state] 新的状态
36
- /// [error] 错误信息(仅在 error 状态时有效)
37
- void setState(ViewState state, {String? error}) {
38
- _state = state;
39
- _errorMessage = error;
40
- notifyListeners();
41
- }
42
-
43
- /// 设置刷新状态
44
- void setRefreshing(bool refreshing) {
45
- _isRefreshing = refreshing;
46
- notifyListeners();
47
- }
48
-
49
- @override
50
- void notifyListeners() {
51
- super.notifyListeners();
52
- }
53
-
54
- /// 通知数据变化(函数级注释)
55
- ///
56
- /// - 仅用于局部刷新,避免整页重建
57
- void notifyDataChange() {
58
- notifyListeners();
59
- }
60
-
61
- // ==================== 生命周期 ====================
62
- /// 初始化钩子(函数级注释)
63
- Future<void> onInit() async {
64
- // 子类可以重写此方法进行初始化
65
- }
66
-
67
- /// 刷新钩子(函数级注释)
68
- Future<void> refreshData() async {
69
- // 子类可以重写此方法实现刷新逻辑
70
- }
71
-
72
- /// 释放资源(函数级注释)
73
- @override
74
- void dispose() {
75
- super.dispose();
76
- }
77
- }
@@ -1,46 +0,0 @@
1
- import 'package:flutter/material.dart';
2
-
3
- /// 状态占位页全局配置(函数级注释)
4
- ///
5
- /// - 统一的 ThemeExtension,用于 Loading/Error/Empty/LoadMore 视图
6
- class StatusViewsTheme extends ThemeExtension<StatusViewsTheme> {
7
- final Widget Function(BuildContext ctx)? loadingBuilder;
8
- final Widget Function(BuildContext ctx, String? msg)? errorBuilder;
9
- final Widget Function(BuildContext ctx)? emptyBuilder;
10
- final Color? refreshIndicatorColor;
11
- final Widget Function(BuildContext ctx, bool hasMore)? loadMoreBuilder;
12
-
13
- const StatusViewsTheme({
14
- this.loadingBuilder,
15
- this.errorBuilder,
16
- this.emptyBuilder,
17
- this.refreshIndicatorColor,
18
- this.loadMoreBuilder,
19
- });
20
-
21
- @override
22
- StatusViewsTheme copyWith({
23
- Widget Function(BuildContext)? loadingBuilder,
24
- Widget Function(BuildContext, String?)? errorBuilder,
25
- Widget Function(BuildContext)? emptyBuilder,
26
- Color? refreshIndicatorColor,
27
- Widget Function(BuildContext, bool)? loadMoreBuilder,
28
- }) {
29
- return StatusViewsTheme(
30
- loadingBuilder: loadingBuilder ?? this.loadingBuilder,
31
- errorBuilder: errorBuilder ?? this.errorBuilder,
32
- emptyBuilder: emptyBuilder ?? this.emptyBuilder,
33
- refreshIndicatorColor:
34
- refreshIndicatorColor ?? this.refreshIndicatorColor,
35
- loadMoreBuilder: loadMoreBuilder ?? this.loadMoreBuilder,
36
- );
37
- }
38
-
39
- @override
40
- ThemeExtension<StatusViewsTheme> lerp(
41
- ThemeExtension<StatusViewsTheme>? other,
42
- double t,
43
- ) {
44
- return this;
45
- }
46
- }