flu-cli-core 1.0.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.
Files changed (56) hide show
  1. package/README.md +60 -0
  2. package/dist/chunk-FOMWV2YP.js +378 -0
  3. package/dist/chunk-SW6YDKXI.js +112 -0
  4. package/dist/factory-6DDXZYQP.js +6 -0
  5. package/dist/index.cjs +4668 -0
  6. package/dist/index.d.cts +644 -0
  7. package/dist/index.d.ts +644 -0
  8. package/dist/index.js +4037 -0
  9. package/dist/upgrade_snippets-XFR7Q444.js +8 -0
  10. package/locales/en-US.json +59 -0
  11. package/locales/zh-CN.json +59 -0
  12. package/package.json +52 -0
  13. package/templates/README.md +129 -0
  14. package/templates/core_files/base/base_list_page.dart.template +225 -0
  15. package/templates/core_files/base/base_list_viewmodel.dart.template +164 -0
  16. package/templates/core_files/base/base_page.dart.template +252 -0
  17. package/templates/core_files/base/base_viewmodel.dart.template +68 -0
  18. package/templates/core_files/base/index.dart.template +5 -0
  19. package/templates/core_files/config/app_config.dart.template +142 -0
  20. package/templates/core_files/config/app_initializer.dart.template +74 -0
  21. package/templates/core_files/config/index.dart.template +3 -0
  22. package/templates/core_files/index.dart.template +8 -0
  23. package/templates/core_files/network/README.md +378 -0
  24. package/templates/core_files/network/app_error_code.dart.template +49 -0
  25. package/templates/core_files/network/app_http.dart.template +306 -0
  26. package/templates/core_files/network/app_response.dart.template +81 -0
  27. package/templates/core_files/network/index.dart.template +12 -0
  28. package/templates/core_files/network/interceptors/app_response_interceptor.dart.template +44 -0
  29. package/templates/core_files/network/interceptors/auth_interceptor.dart.template +30 -0
  30. package/templates/core_files/network/interceptors/error_interceptor.dart.template +48 -0
  31. package/templates/core_files/network/interceptors/index.dart.template +6 -0
  32. package/templates/core_files/network/interceptors/log_interceptor.dart.template +97 -0
  33. package/templates/core_files/network/interceptors/network_error_interceptor.dart.template +58 -0
  34. package/templates/core_files/network/interceptors/retry_interceptor.dart.template +69 -0
  35. package/templates/core_files/network/response_adapter.dart.template +69 -0
  36. package/templates/core_files/router/app_routes.dart.template +32 -0
  37. package/templates/core_files/router/index.dart.template +3 -0
  38. package/templates/core_files/router/navigator_util_getx.dart.template +131 -0
  39. package/templates/core_files/router/navigator_util_material.dart.template +191 -0
  40. package/templates/core_files/storage/index.dart.template +3 -0
  41. package/templates/core_files/storage/storage_keys.dart.template +34 -0
  42. package/templates/core_files/storage/storage_util.dart.template +102 -0
  43. package/templates/core_files/theme/app_theme.dart.template +37 -0
  44. package/templates/core_files/theme/index.dart.template +3 -0
  45. package/templates/core_files/theme/status_views_theme.dart.template +40 -0
  46. package/templates/core_files/utils/index.dart.template +2 -0
  47. package/templates/core_files/utils/loading_util.dart.template +55 -0
  48. package/templates/core_files/utils/toast_util.dart.template +128 -0
  49. package/templates/examples/eg_list_page.dart.template +340 -0
  50. package/templates/examples/eg_list_viewmodel.dart.template +31 -0
  51. package/templates/examples/eg_service.dart.template +78 -0
  52. package/templates/examples/mock_data.dart.template +50388 -0
  53. package/templates/examples/tu_chong_model.dart.template +633 -0
  54. package/templates/request_helper.dart.template +59 -0
  55. package/templates/snippets/flu-cli.code-snippets +268 -0
  56. package/templates/snippets/flu-cli.code-snippets.backup +268 -0
@@ -0,0 +1,633 @@
1
+ import 'dart:convert' show json, jsonEncode;
2
+ import 'dart:math';
3
+ import 'package:flutter/rendering.dart';
4
+
5
+ void tryCatch(Function f) {
6
+ try {
7
+ f.call();
8
+ } catch (e, stack) {
9
+ debugPrint('$e');
10
+ debugPrint('$stack');
11
+ }
12
+ }
13
+
14
+ T? asT<T>(dynamic value) {
15
+ if (value is T) {
16
+ return value;
17
+ }
18
+ if (value != null) {
19
+ final String valueS = value.toString();
20
+ if (0 is T) {
21
+ return int.tryParse(valueS) as T?;
22
+ } else if (0.0 is T) {
23
+ return double.tryParse(valueS) as T?;
24
+ } else if ('' is T) {
25
+ return valueS as T;
26
+ } else if (false is T) {
27
+ if (valueS == '0' || valueS == '1') {
28
+ return (valueS == '1') as T;
29
+ }
30
+ return (valueS == 'true') as T;
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+
36
+ class TuChongSource {
37
+ TuChongSource({
38
+ this.counts,
39
+ this.feedList,
40
+ this.isHistory,
41
+ this.message,
42
+ this.more,
43
+ this.result,
44
+ });
45
+
46
+ factory TuChongSource.fromJson(Map<String, dynamic> jsonRes) {
47
+ final List<TuChongItem>? feedList =
48
+ jsonRes['feedList'] is List ? <TuChongItem>[] : null;
49
+ if (feedList != null) {
50
+ for (final dynamic item in jsonRes['feedList']) {
51
+ if (item != null) {
52
+ tryCatch(() {
53
+ feedList
54
+ .add(TuChongItem.fromJson(asT<Map<String, dynamic>>(item)!));
55
+ });
56
+ }
57
+ }
58
+ }
59
+
60
+ return TuChongSource(
61
+ counts: asT<int>(jsonRes['counts']),
62
+ feedList: feedList,
63
+ isHistory: asT<bool>(jsonRes['is_history']),
64
+ message: asT<String>(jsonRes['message']),
65
+ more: asT<bool>(jsonRes['more']),
66
+ result: asT<String>(jsonRes['result']),
67
+ );
68
+ }
69
+
70
+ final int? counts;
71
+ final List<TuChongItem>? feedList;
72
+ final bool? isHistory;
73
+ final String? message;
74
+ final bool? more;
75
+ final String? result;
76
+
77
+ Map<String, dynamic> toJson() => <String, dynamic>{
78
+ 'counts': counts,
79
+ 'feedList': feedList,
80
+ 'is_history': isHistory,
81
+ 'message': message,
82
+ 'more': more,
83
+ 'result': result,
84
+ };
85
+
86
+ @override
87
+ String toString() {
88
+ return json.encode(this);
89
+ }
90
+ }
91
+
92
+ class TuChongItem {
93
+ TuChongItem({
94
+ this.authorId,
95
+ this.collected,
96
+ this.commentListPrefix,
97
+ this.comments,
98
+ this.content,
99
+ this.createdAt,
100
+ this.dataType,
101
+ this.delete,
102
+ this.eventTags,
103
+ this.excerpt,
104
+ this.favoriteListPrefix,
105
+ this.favorites,
106
+ this.imageCount,
107
+ this.images,
108
+ this.isFavorite,
109
+ this.parentComments,
110
+ this.passedTime,
111
+ this.postId,
112
+ this.publishedAt,
113
+ this.recommend,
114
+ this.recomType,
115
+ this.rewardable,
116
+ this.rewardListPrefix,
117
+ this.rewards,
118
+ this.rqtId,
119
+ this.shares,
120
+ this.site,
121
+ this.siteId,
122
+ this.sites,
123
+ this.tags,
124
+ this.title,
125
+ this.titleImage,
126
+ this.type,
127
+ this.update,
128
+ this.url,
129
+ this.views,
130
+ this.tagColors,
131
+ this.index,
132
+ this.crossAxisCount,
133
+ this.textimgurls,
134
+ this.realImages,
135
+ this.netData,
136
+ });
137
+
138
+ factory TuChongItem.fromJson(Map<String, dynamic> jsonRes) {
139
+ final List<Object?>? commentListPrefix =
140
+ jsonRes['comment_list_prefix'] is List ? <Object?>[] : null;
141
+ if (commentListPrefix != null) {
142
+ for (final dynamic item in jsonRes['comment_list_prefix']) {
143
+ if (item != null) {
144
+ tryCatch(() {
145
+ commentListPrefix.add(asT<Object>(item));
146
+ });
147
+ }
148
+ }
149
+ }
150
+
151
+ final List<String?>? eventTags =
152
+ jsonRes['event_tags'] is List ? <String?>[] : null;
153
+ if (eventTags != null) {
154
+ for (final dynamic item in jsonRes['event_tags']) {
155
+ if (item != null) {
156
+ tryCatch(() {
157
+ eventTags.add(asT<String>(item));
158
+ });
159
+ }
160
+ }
161
+ }
162
+
163
+ final List<Object?>? favoriteListPrefix =
164
+ jsonRes['favorite_list_prefix'] is List ? <Object?>[] : null;
165
+ if (favoriteListPrefix != null) {
166
+ for (final dynamic item in jsonRes['favorite_list_prefix']) {
167
+ if (item != null) {
168
+ tryCatch(() {
169
+ favoriteListPrefix.add(asT<Object>(item));
170
+ });
171
+ }
172
+ }
173
+ }
174
+
175
+ final List<ImageItem>? images =
176
+ jsonRes['images'] is List ? <ImageItem>[] : null;
177
+ int cACount = 1;
178
+ List<String> realList = [];
179
+ if (images != null) {
180
+ for (final dynamic item in jsonRes['images']) {
181
+ if (item != null) {
182
+ tryCatch(() {
183
+ final ImageItem imageItem =
184
+ ImageItem.fromJson(asT<Map<String, dynamic>>(item)!);
185
+ images.add(imageItem);
186
+ realList.add(imageItem.imageUrl);
187
+ });
188
+ }
189
+ }
190
+ if (images.length == 1) {
191
+ cACount = 1;
192
+ } else if (images.length == 4 || images.length == 2) {
193
+ cACount = 2;
194
+ } else {
195
+ cACount = 3;
196
+ }
197
+ } else {
198
+ if (jsonRes['text_img_urls'] != null) {
199
+ realList = jsonRes['text_img_urls'];
200
+ }
201
+ }
202
+
203
+ final List<Object?>? rewardListPrefix =
204
+ jsonRes['reward_list_prefix'] is List ? <Object?>[] : null;
205
+ if (rewardListPrefix != null) {
206
+ for (final dynamic item in jsonRes['reward_list_prefix']) {
207
+ if (item != null) {
208
+ tryCatch(() {
209
+ rewardListPrefix.add(asT<Object>(item));
210
+ });
211
+ }
212
+ }
213
+ }
214
+
215
+ final List<Object?>? sites = jsonRes['sites'] is List ? <Object?>[] : null;
216
+ if (sites != null) {
217
+ for (final dynamic item in jsonRes['sites']) {
218
+ if (item != null) {
219
+ tryCatch(() {
220
+ sites.add(asT<Object>(item));
221
+ });
222
+ }
223
+ }
224
+ }
225
+ final List<Color> tagColors = <Color>[];
226
+ final List<String?>? tags = jsonRes['tags'] is List ? <String?>[] : null;
227
+ if (tags != null) {
228
+ const int maxNum = 6;
229
+ for (final dynamic item in jsonRes['tags']) {
230
+ if (item != null) {
231
+ tryCatch(() {
232
+ tags.add(asT<String>(item));
233
+ tagColors.add(
234
+ Color.fromARGB(
235
+ 255,
236
+ Random.secure().nextInt(255),
237
+ Random.secure().nextInt(255),
238
+ Random.secure().nextInt(255),
239
+ ),
240
+ );
241
+ });
242
+ }
243
+ if (tags.length == maxNum) {
244
+ break;
245
+ }
246
+ }
247
+ }
248
+
249
+ return TuChongItem(
250
+ authorId: asT<String>(jsonRes['author_id']),
251
+ collected: asT<bool>(jsonRes['collected']),
252
+ commentListPrefix: commentListPrefix,
253
+ comments: asT<int>(jsonRes['comments']),
254
+ content: asT<String>(jsonRes['content']),
255
+ createdAt: asT<String>(jsonRes['created_at']),
256
+ dataType: asT<String>(jsonRes['data_type']),
257
+ delete: asT<bool>(jsonRes['delete']),
258
+ eventTags: eventTags,
259
+ excerpt: asT<String>(jsonRes['excerpt']),
260
+ favoriteListPrefix: favoriteListPrefix,
261
+ favorites: asT<int>(jsonRes['favorites']),
262
+ imageCount: asT<int>(jsonRes['image_count']),
263
+ images: images,
264
+ isFavorite: asT<bool>(jsonRes['is_favorite']),
265
+ parentComments: asT<String>(jsonRes['parent_comments']),
266
+ passedTime: asT<String>(jsonRes['passed_time']),
267
+ postId: asT<int>(jsonRes['post_id']),
268
+ publishedAt: asT<String>(jsonRes['published_at']),
269
+ recommend: asT<bool>(jsonRes['recommend']),
270
+ recomType: asT<String>(jsonRes['recom_type']),
271
+ rewardable: asT<bool>(jsonRes['rewardable']),
272
+ rewardListPrefix: rewardListPrefix,
273
+ rewards: asT<String>(jsonRes['rewards']),
274
+ rqtId: asT<String>(jsonRes['rqt_id']),
275
+ shares: asT<int>(jsonRes['shares']),
276
+ site: Site.fromJson(asT<Map<String, dynamic>>(jsonRes['site'])!),
277
+ siteId: asT<String>(jsonRes['site_id']),
278
+ sites: sites,
279
+ tags: tags,
280
+ tagColors: tagColors,
281
+ title: asT<String>(jsonRes['title']),
282
+ titleImage: jsonRes['title_image'] == null
283
+ ? null
284
+ : TitleImage.fromJson(
285
+ asT<Map<String, dynamic>>(jsonRes['title_image'])!,
286
+ ),
287
+ type: asT<String>(jsonRes['type']),
288
+ update: asT<bool>(jsonRes['update']),
289
+ url: asT<String>(jsonRes['url']),
290
+ views: asT<int>(jsonRes['views']),
291
+ crossAxisCount: cACount,
292
+ textimgurls: jsonRes['text_img_urls'],
293
+ realImages: realList,
294
+ netData: jsonRes,
295
+ );
296
+ }
297
+
298
+ final String? authorId;
299
+ final bool? collected;
300
+ final List<Object?>? commentListPrefix;
301
+ final int? comments;
302
+ final String? content;
303
+ final String? createdAt;
304
+ final String? dataType;
305
+ final bool? delete;
306
+ final List<String?>? eventTags;
307
+ final String? excerpt;
308
+ final List<Object?>? favoriteListPrefix;
309
+ int? favorites;
310
+ final int? imageCount;
311
+ final List<ImageItem>? images;
312
+ bool? isFavorite;
313
+ final String? parentComments;
314
+ final String? passedTime;
315
+ final int? postId;
316
+ final String? publishedAt;
317
+ final bool? recommend;
318
+ final String? recomType;
319
+ final bool? rewardable;
320
+ final List<Object?>? rewardListPrefix;
321
+ final String? rewards;
322
+ final String? rqtId;
323
+ final int? shares;
324
+ final Site? site;
325
+ final String? siteId;
326
+ final List<Object?>? sites;
327
+ final List<String?>? tags;
328
+ final String? title;
329
+ final TitleImage? titleImage;
330
+ final String? type;
331
+ final bool? update;
332
+ final String? url;
333
+ final int? views;
334
+ final List<Color>? tagColors;
335
+ final List? textimgurls;
336
+
337
+ final List<String>? realImages;
338
+
339
+ final Map<String, dynamic>? netData;
340
+ int? crossAxisCount = 1;
341
+
342
+ int? index = 0;
343
+
344
+ bool get hasImage {
345
+ return images != null && images!.isNotEmpty;
346
+ }
347
+
348
+ Size? imageRawSize;
349
+
350
+ Size get imageSize {
351
+ if (!hasImage) {
352
+ return const Size(0, 0);
353
+ }
354
+ return Size(images![0].width!.toDouble(), images![0].height!.toDouble());
355
+ }
356
+
357
+ String get imageUrl {
358
+ if (!hasImage) {
359
+ return '';
360
+ }
361
+ return 'https://photo.tuchong.com/${images![0].userId}/f/${images![0].imgId}.jpg';
362
+ }
363
+
364
+ String? get avatarUrl => site!.icon;
365
+
366
+ String? get imageTitle {
367
+ if (!hasImage) {
368
+ return title;
369
+ }
370
+
371
+ return images![0].title;
372
+ }
373
+
374
+ String? get imageDescription {
375
+ if (!hasImage) {
376
+ return content;
377
+ }
378
+
379
+ return images![0].description;
380
+ }
381
+
382
+ Map<String, dynamic> toJson() => <String, dynamic>{
383
+ 'author_id': authorId,
384
+ 'collected': collected,
385
+ 'comment_list_prefix': commentListPrefix,
386
+ 'comments': comments,
387
+ 'content': content,
388
+ 'created_at': createdAt,
389
+ 'data_type': dataType,
390
+ 'delete': delete,
391
+ 'event_tags': eventTags,
392
+ 'excerpt': excerpt,
393
+ 'favorite_list_prefix': favoriteListPrefix,
394
+ 'favorites': favorites,
395
+ 'image_count': imageCount,
396
+ 'images': images,
397
+ 'is_favorite': isFavorite,
398
+ 'parent_comments': parentComments,
399
+ 'passed_time': passedTime,
400
+ 'post_id': postId,
401
+ 'published_at': publishedAt,
402
+ 'recommend': recommend,
403
+ 'recom_type': recomType,
404
+ 'rewardable': rewardable,
405
+ 'reward_list_prefix': rewardListPrefix,
406
+ 'rewards': rewards,
407
+ 'rqt_id': rqtId,
408
+ 'shares': shares,
409
+ 'site': site,
410
+ 'site_id': siteId,
411
+ 'sites': sites,
412
+ 'tags': tags,
413
+ 'title': title,
414
+ 'title_image': titleImage?.toJson(),
415
+ 'type': type,
416
+ 'update': update,
417
+ 'url': url,
418
+ 'views': views,
419
+ };
420
+
421
+ @override
422
+ String toString() {
423
+ return json.encode(this);
424
+ }
425
+ }
426
+
427
+ class ImageItem {
428
+ ImageItem({
429
+ this.description,
430
+ this.excerpt,
431
+ this.height,
432
+ this.imgId,
433
+ this.imgIdStr,
434
+ this.title,
435
+ this.userId,
436
+ this.width,
437
+ });
438
+
439
+ factory ImageItem.fromJson(Map<String, dynamic> jsonRes) => ImageItem(
440
+ description: asT<String>(jsonRes['description']),
441
+ excerpt: asT<String>(jsonRes['excerpt']),
442
+ height: asT<int>(jsonRes['height']),
443
+ imgId: asT<int>(jsonRes['img_id']),
444
+ imgIdStr: asT<String>(jsonRes['img_id_str']),
445
+ title: asT<String>(jsonRes['title']),
446
+ userId: asT<int>(jsonRes['user_id']),
447
+ width: asT<int>(jsonRes['width']),
448
+ );
449
+
450
+ final String? description;
451
+ final String? excerpt;
452
+ final int? height;
453
+ final int? imgId;
454
+ final String? imgIdStr;
455
+ final String? title;
456
+ final int? userId;
457
+ final int? width;
458
+ String get imageUrl {
459
+ return 'https://photo.tuchong.com/$userId/f/$imgId.jpg';
460
+ }
461
+ // ImageProvider createNetworkImage() {
462
+ // return ExtendedNetworkImageProvider(imageUrl);
463
+ // }
464
+
465
+ // ImageProvider createResizeImage() {
466
+ // return ResizeImage(ExtendedNetworkImageProvider(imageUrl),
467
+ // width: width ~/ 5, height: height ~/ 5);
468
+ // }
469
+
470
+ // void clearCache() {
471
+ // createNetworkImage().evict();
472
+ // createResizeImage().evict();
473
+ // }
474
+ Map<String, dynamic> toJson() => <String, dynamic>{
475
+ 'description': description,
476
+ 'excerpt': excerpt,
477
+ 'height': height,
478
+ 'img_id': imgId,
479
+ 'img_id_str': imgIdStr,
480
+ 'title': title,
481
+ 'user_id': userId,
482
+ 'width': width,
483
+ };
484
+
485
+ @override
486
+ String toString() {
487
+ return json.encode(this);
488
+ }
489
+ }
490
+
491
+ class Site {
492
+ Site({
493
+ this.description,
494
+ this.domain,
495
+ this.followers,
496
+ this.hasEverphotoNote,
497
+ this.icon,
498
+ this.isBindEverphoto,
499
+ this.isFollowing,
500
+ this.name,
501
+ this.siteId,
502
+ this.type,
503
+ this.url,
504
+ this.verificationList,
505
+ this.verifications,
506
+ this.verified,
507
+ });
508
+
509
+ factory Site.fromJson(Map<String, dynamic> jsonRes) {
510
+ final List<VerificationList>? verificationList =
511
+ jsonRes['verification_list'] is List ? <VerificationList>[] : null;
512
+ if (verificationList != null) {
513
+ for (final dynamic item in jsonRes['verification_list']) {
514
+ if (item != null) {
515
+ tryCatch(() {
516
+ verificationList.add(
517
+ VerificationList.fromJson(asT<Map<String, dynamic>>(item)!),
518
+ );
519
+ });
520
+ }
521
+ }
522
+ }
523
+
524
+ return Site(
525
+ description: asT<String>(jsonRes['description']),
526
+ domain: asT<String>(jsonRes['domain']),
527
+ followers: asT<int>(jsonRes['followers']),
528
+ hasEverphotoNote: asT<bool>(jsonRes['has_everphoto_note']),
529
+ icon: asT<String>(jsonRes['icon']),
530
+ isBindEverphoto: asT<bool>(jsonRes['is_bind_everphoto']),
531
+ isFollowing: asT<bool>(jsonRes['is_following']),
532
+ name: asT<String>(jsonRes['name']),
533
+ siteId: asT<String>(jsonRes['site_id']),
534
+ type: asT<String>(jsonRes['type']),
535
+ url: asT<String>(jsonRes['url']),
536
+ verificationList: verificationList,
537
+ verifications: asT<int>(jsonRes['verifications']),
538
+ verified: asT<bool>(jsonRes['verified']),
539
+ );
540
+ }
541
+
542
+ final String? description;
543
+ final String? domain;
544
+ int? followers;
545
+ final bool? hasEverphotoNote;
546
+ final String? icon;
547
+ final bool? isBindEverphoto;
548
+ bool? isFollowing;
549
+ final String? name;
550
+ final String? siteId;
551
+ final String? type;
552
+ final String? url;
553
+ final List<VerificationList>? verificationList;
554
+ final int? verifications;
555
+ final bool? verified;
556
+
557
+ Map<String, dynamic> toJson() => <String, dynamic>{
558
+ 'description': description,
559
+ 'domain': domain,
560
+ 'followers': followers,
561
+ 'has_everphoto_note': hasEverphotoNote,
562
+ 'icon': icon,
563
+ 'is_bind_everphoto': isBindEverphoto,
564
+ 'is_following': isFollowing,
565
+ 'name': name,
566
+ 'site_id': siteId,
567
+ 'type': type,
568
+ 'url': url,
569
+ 'verification_list': verificationList,
570
+ 'verifications': verifications,
571
+ 'verified': verified,
572
+ };
573
+
574
+ @override
575
+ String toString() {
576
+ return json.encode(this);
577
+ }
578
+ }
579
+
580
+ class VerificationList {
581
+ VerificationList({
582
+ this.verificationReason,
583
+ this.verificationType,
584
+ });
585
+
586
+ factory VerificationList.fromJson(Map<String, dynamic> jsonRes) =>
587
+ VerificationList(
588
+ verificationReason: asT<String>(jsonRes['verification_reason']),
589
+ verificationType: asT<int>(jsonRes['verification_type']),
590
+ );
591
+
592
+ final String? verificationReason;
593
+ final int? verificationType;
594
+
595
+ Map<String, dynamic> toJson() => <String, dynamic>{
596
+ 'verification_reason': verificationReason,
597
+ 'verification_type': verificationType,
598
+ };
599
+
600
+ @override
601
+ String toString() {
602
+ return json.encode(this);
603
+ }
604
+ }
605
+
606
+ class TitleImage {
607
+ const TitleImage({
608
+ this.height,
609
+ this.url,
610
+ this.width,
611
+ });
612
+
613
+ factory TitleImage.fromJson(Map<String, dynamic> jsonRes) => TitleImage(
614
+ height: asT<int?>(jsonRes['height']),
615
+ url: asT<String?>(jsonRes['url']),
616
+ width: asT<int?>(jsonRes['width']),
617
+ );
618
+
619
+ final int? height;
620
+ final String? url;
621
+ final int? width;
622
+
623
+ @override
624
+ String toString() {
625
+ return jsonEncode(this);
626
+ }
627
+
628
+ Map<String, dynamic> toJson() => <String, dynamic>{
629
+ 'height': height,
630
+ 'url': url,
631
+ 'width': width,
632
+ };
633
+ }
@@ -0,0 +1,59 @@
1
+ import 'package:flutter/material.dart';
2
+ import '../network/http_util.dart';
3
+ import 'loading_util.dart';
4
+ import 'toast_util.dart';
5
+
6
+ /// 网络请求辅助工具
7
+ ///
8
+ /// 职责:
9
+ /// 1. 封装通用的网络请求交互逻辑(Loading、错误提示)。
10
+ /// 2. 解耦业务层与 UI 工具层。
11
+ class RequestHelper {
12
+ /// 带 Loading 和自动错误提示的请求包装
13
+ ///
14
+ /// [context] 必传,用于展示弹窗和 Toast
15
+ /// [request] 具体的网络请求闭包
16
+ /// [loadingMessage] 自定义加载提示语
17
+ /// [showError] 是否在失败时自动展示 Toast
18
+ /// [showSuccess] 是否在成功时展示 Toast
19
+ /// [successMessage] 成功提示语
20
+ static Future<T> withLoading<T>({
21
+ required BuildContext context,
22
+ required Future<T> Function() request,
23
+ String? loadingMessage,
24
+ bool showError = true,
25
+ bool showSuccess = false,
26
+ String? successMessage,
27
+ }) async {
28
+ LoadingUtil.show(context, message: loadingMessage ?? '加载中...');
29
+
30
+ try {
31
+ final result = await request();
32
+ LoadingUtil.dismiss();
33
+
34
+ // 在 await 之后使用 context 前检查 mounted
35
+ if (showSuccess && context.mounted) {
36
+ ToastUtil.show(context, successMessage ?? '操作成功');
37
+ }
38
+
39
+ return result;
40
+ } catch (e) {
41
+ LoadingUtil.dismiss();
42
+
43
+ // 在 await 之后使用 context 前检查 mounted
44
+ if (showError && context.mounted) {
45
+ ToastUtil.show(context, _getErrorMessage(e));
46
+ }
47
+
48
+ rethrow;
49
+ }
50
+ }
51
+
52
+ /// 集中解析错误信息
53
+ static String _getErrorMessage(dynamic error) {
54
+ if (error is ServerException) return error.message;
55
+ if (error is NetworkException) return error.message;
56
+ if (error is Exception) return error.toString();
57
+ return '请求遇到了点问题,请重试';
58
+ }
59
+ }