Browse Source

通知列表完成,LoadingView + Refresh 刷新,加载更多的模板。分组悬停模板

liukai 2 weeks ago
parent
commit
6dc88fe653

+ 1 - 1
packages/cpt_main/lib/modules/me/me_view_model.g.dart

@@ -6,7 +6,7 @@ part of 'me_view_model.dart';
 // RiverpodGenerator
 // **************************************************************************
 
-String _$meViewModelHash() => r'43b1e02ae1e52bf7c6abf994849fb9a10d937b34';
+String _$meViewModelHash() => r'2877de7ca5a56800fa659c495a0bf3ff59bd247a';
 
 /// See also [MeViewModel].
 @ProviderFor(MeViewModel)

+ 99 - 0
packages/cpt_main/lib/modules/notification/item_notification.dart

@@ -0,0 +1,99 @@
+import 'package:cpt_main/modules/demo_page.dart';
+import 'package:cpt_main/modules/notification/notification_group_data.dart';
+import 'package:cs_resources/generated/assets.dart';
+import 'package:cs_resources/theme/app_colors_theme.dart';
+import 'package:flutter/material.dart';
+import 'package:widgets/ext/ex_widget.dart';
+import 'package:widgets/my_load_image.dart';
+import 'package:widgets/my_text_view.dart';
+
+class NotificationItem extends StatelessWidget {
+  final String item;
+  final int index;
+
+  const NotificationItem({
+    required this.item,
+    required this.index,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      width: double.infinity,
+      color: context.appColors.whiteBG,
+      padding: const EdgeInsets.only(left: 11, right: 15, top: 10.5),
+      child: Column(
+        children: [
+          Row(
+            children: [
+              const MyAssetImage(Assets.mainNotificationItemIcon, width: 42.5, height: 42.5).marginOnly(right: 16),
+              Column(
+                children: [
+                  //标题
+                  Row(
+                    crossAxisAlignment: CrossAxisAlignment.center,
+                    children: [
+                      //未读的小圆点
+                      Container(
+                        width: 6,
+                        height: 6,
+                        margin: const EdgeInsets.only(right: 8.5),
+                        decoration: BoxDecoration(
+                          shape: BoxShape.circle,
+                          color: context.appColors.tabBgSelectedPrimary,
+                        ),
+                      ),
+
+                      //消息标题
+                      MyTextView(
+                        "NEW ANNOUNCEMENT",
+                        fontSize: 16,
+                        isFontBold: true,
+                        textColor: context.appColors.textBlack,
+                        maxLines: 2,
+                      ).expanded(),
+
+                      //消息时间
+                      MyTextView(
+                        "10:19 AM",
+                        fontSize: 13,
+                        isFontRegular: true,
+                        textColor: context.appColors.textLightPurple,
+                      ),
+                    ],
+                  ),
+
+                  //内容
+                  Row(
+                    children: [
+                      //文本内容
+                      MyTextView(
+                        "Standard operating procedure for replacement vehicles andover night parking vehicles.",
+                        fontSize: 15,
+                        marginRight: 15,
+                        isFontRegular: true,
+                        textColor: context.appColors.textBlack,
+                      ).expanded(),
+
+                      //图片
+                      MyLoadImage(
+                        "https://img1.baidu.com/it/u=1656098746,3560654086&fm=253&fmt=auto&app=120&f=JPEG?w=800&h=800",
+                        width: 87.5,
+                        height: 60.5,
+                      ),
+                    ],
+                  ).marginOnly(top: 9),
+                ],
+              ).expanded(),
+            ],
+          ),
+          const SizedBox(height: 17.5),
+          Divider(
+            height: 0.5,
+            color: context.appColors.backgroundDark,
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 26 - 0
packages/cpt_main/lib/modules/notification/item_notification_header.dart

@@ -0,0 +1,26 @@
+import 'package:cs_resources/theme/app_colors_theme.dart';
+import 'package:flutter/material.dart';
+import 'package:widgets/my_text_view.dart';
+
+class NotificationItemHeader extends StatelessWidget {
+  String? groupId;
+
+  NotificationItemHeader(this.groupId);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      width: double.infinity,
+      color: context.appColors.backgroundDark,
+      padding: const EdgeInsets.symmetric(horizontal: 14),
+      child: MyTextView(
+        groupId ?? "-",
+        fontSize: 14,
+        paddingTop: 8,
+        paddingBottom: 8,
+        isFontMedium: true,
+        textColor: context.appColors.textBlack,
+      ),
+    );
+  }
+}

+ 6 - 0
packages/cpt_main/lib/modules/notification/notification_group_data.dart

@@ -0,0 +1,6 @@
+
+//对应分组的数据,需要在ViewModel中处理每一个组的数据
+class NotificationGroupData{
+  String? groupId;
+  List<String>? groupDatas=[];
+}

+ 78 - 7
packages/cpt_main/lib/modules/notification/notification_page.dart

@@ -1,11 +1,23 @@
+import 'package:cpt_main/modules/notification/item_notification_header.dart';
 import 'package:cpt_main/router/page/main_page_router.dart';
+import 'package:cs_resources/generated/l10n.dart';
+import 'package:cs_resources/theme/app_colors_theme.dart';
 import 'package:flutter/material.dart';
 import 'package:auto_route/auto_route.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:router/ext/auto_router_extensions.dart';
+import 'package:widgets/ext/ex_widget.dart';
+import 'package:widgets/load_state_layout.dart';
+import 'package:widgets/my_appbar.dart';
+import 'package:widgets/my_text_view.dart';
+import 'package:widgets/widget_export.dart';
 
-@RoutePage()
-class NotificationPage extends StatelessWidget {
+import 'item_notification.dart';
+import 'notification_view_model.dart';
 
+@RoutePage()
+class NotificationPage extends HookConsumerWidget {
   const NotificationPage({Key? key}) : super(key: key);
 
   static void startInstance({BuildContext? context}) {
@@ -17,12 +29,71 @@ class NotificationPage extends StatelessWidget {
   }
 
   @override
-  Widget build(BuildContext context) {
+  Widget build(BuildContext context, WidgetRef ref) {
+    final viewModel = ref.read(notificationViewModelProvider.notifier);
+    final state = ref.watch(notificationViewModelProvider);
+
+    useEffect(() {
+      // 组件挂载时执行 - 执行接口请求
+      Future.microtask(() => viewModel.fetchList());
+      return () {
+        // 组件卸载时执行
+      };
+    }, []);
+
     return Scaffold(
-      appBar: AppBar(title: Text("NotificationPage")),
-      body: Center(
-        child: Text("Notification Page"),
+      appBar: MyAppBar.appBar(context, S.current.notification,
+          backgroundColor: context.appColors.whiteBG,
+          actions: [
+            Center(
+                child: MyTextView(
+              S.current.mark_all,
+              fontSize: 16,
+              textColor: context.appColors.textPrimary,
+              onClick: viewModel.markAll,
+              isFontRegular: true,
+              marginRight: 15,
+            )),
+          ],
+          showBottomDivider: false),
+      backgroundColor: context.appColors.whiteBG,
+      body: SizedBox(
+        width: double.infinity,
+        height: double.infinity,
+        child: EasyRefresh(
+          controller: viewModel.refreshController,
+          onRefresh: viewModel.onRefresh,
+          onLoad: viewModel.loadMore,
+          child: LoadStateLayout(
+            state: state.loadingState,
+            errorMessage: state.errorMessage,
+            errorRetry: () {
+              viewModel.retryRequest();
+            },
+            successSliverWidget: [
+              SliverList(
+                  delegate: SliverChildBuilderDelegate(
+                (context, index) {
+                  return StickyHeader(
+                    header: NotificationItemHeader(state.datas[index].groupId),
+                    content: Column(
+                      children: _buildGroup(state.datas[index].groupDatas!, index),
+                    ),
+                  );
+                },
+                childCount: state.datas.length,
+              ))
+            ],
+          ),
+        ),
       ),
     );
   }
-}
+
+  //当前组内的子数据
+  _buildGroup(List<String> list, int index) {
+    return list.map((item) {
+      return NotificationItem(item: item, index: index);
+    }).toList();
+  }
+}

+ 31 - 0
packages/cpt_main/lib/modules/notification/notification_state.dart

@@ -0,0 +1,31 @@
+import 'package:cpt_main/modules/notification/notification_group_data.dart';
+import 'package:widgets/load_state_layout.dart';
+
+class NotificationState {
+  //页面 LoadView 状态的展示
+  LoadState loadingState;
+  String? errorMessage;
+
+  List<NotificationGroupData> datas; //页面列表数据
+
+  // ===================================  Begin  ↓  ===================================
+
+  NotificationState({
+    this.loadingState = LoadState.State_Loading,
+    this.errorMessage,
+    required this.datas,
+  });
+
+  NotificationState copyWith({
+    LoadState? loadingState,
+    String? errorMessage,
+    bool? needShowPlaceholder,
+    List<NotificationGroupData>? datas,
+  }) {
+    return NotificationState(
+      errorMessage: errorMessage ?? this.errorMessage,
+      loadingState: loadingState ?? this.loadingState,
+      datas: datas ?? this.datas,
+    );
+  }
+}

+ 159 - 0
packages/cpt_main/lib/modules/notification/notification_view_model.dart

@@ -0,0 +1,159 @@
+import 'package:cpt_main/modules/notification/notification_group_data.dart';
+import 'package:plugin_platform/engine/toast/toast_engine.dart';
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+import 'package:widgets/load_state_layout.dart';
+import 'package:widgets/widget_export.dart';
+
+import 'notification_state.dart';
+
+part 'notification_view_model.g.dart';
+
+@riverpod
+class NotificationViewModel extends _$NotificationViewModel {
+  @override
+  NotificationState build() {
+    return NotificationState(datas: []);
+  }
+
+  var _curPage = 1; //请求参数当前的页面
+  var _needShowPlaceholder = true; //是否展示LoadingView
+
+  // Refresh 控制器
+  final EasyRefreshController refreshController = EasyRefreshController(
+    controlFinishRefresh: true, //允许刷新
+    controlFinishLoad: true, //允许加载
+  );
+
+  //刷新页面状态
+  void changeLoadingState(LoadState loadState, String? errorMsg) {
+    state = state.copyWith(loadingState: loadState, errorMessage: errorMsg);
+  }
+
+  // Refresh 刷新事件
+  Future onRefresh() async {
+    _curPage = 1;
+    fetchList();
+  }
+
+  // Refresh 加载事件
+  Future loadMore() async {
+    _curPage++;
+    fetchList();
+  }
+
+  // 重试请求
+  Future retryRequest() async {
+    _curPage = 1;
+    _needShowPlaceholder = true;
+    fetchList();
+  }
+
+  /// 获取服务器数据
+  Future fetchList() async {
+    if (_needShowPlaceholder) {
+      changeLoadingState(LoadState.State_Loading, null);
+    }
+
+    // 获取 Applied 列表
+    // var listResult = await _jobRepository.fetchJobAppliedList(
+    //   state.jobId,
+    //   state.selectedStatusId,
+    //   state.keyword,
+    //   curPage: _curPage,
+    //   cancelToken: cancelToken,
+    // );
+    //
+    // // 处理数据
+    // if (listResult.isSuccess) {
+    //   handleList(listResult.data?.rows);
+    // } else {
+    //   errorMessage = listResult.errorMsg;
+    //   changeLoadingState(LoadState.State_Error);
+    // }
+
+    await Future.delayed(const Duration(milliseconds: 1500));
+
+    List<NotificationGroupData> list = [];
+    if (_curPage > 1) {
+
+      //这里只加载一页吧
+    } else {
+
+      list.add(NotificationGroupData()
+        ..groupId = "Toady"
+        ..groupDatas = ["1", "2", "3", "4"]);
+
+      list.add(NotificationGroupData()
+        ..groupId = "Friday 11 oct 2024"
+        ..groupDatas = ["1", "2", "3"]);
+
+      list.add(NotificationGroupData()
+        ..groupId = "Thursday 10 oct 2024"
+        ..groupDatas = ["1", "2"]);
+
+      list.add(NotificationGroupData()
+        ..groupId = "Wednesday 9 oct 2024"
+        ..groupDatas = ["1"]);
+
+    }
+
+
+    if (_curPage == 1) {
+      //刷新的方式
+      state = state.copyWith(datas: list);
+      refreshController.finishRefresh();
+
+      //更新展示的状态
+      changeLoadingState(LoadState.State_Success, null);
+    } else {
+      //加载更多
+      final allList = state.datas;
+      allList.addAll(list);
+      state.datas.addAll(list);
+
+      // refreshController.finishLoad();
+      refreshController.finishLoad(IndicatorResult.noMore);
+
+      state = state.copyWith(datas: allList);
+    }
+
+    // 最后赋值
+    _needShowPlaceholder = false;
+  }
+
+// 处理数据与展示的逻辑
+// void handleList(List<JobAppliedListSGRows>? list) {
+//   if (list != null && list.isNotEmpty) {
+//     //有数据,判断是刷新还是加载更多的数据
+//     if (_curPage == 1) {
+//       //刷新的方式
+//       state.datas.clear();
+//       state.datas.addAll(list);
+//       refreshController.finishRefresh();
+//
+//       //更新展示的状态
+//       changeLoadingState(LoadState.State_Success);
+//     } else {
+//       //加载更多
+//       state.datas.addAll(list);
+//       refreshController.finishLoad();
+//       update();
+//     }
+//   } else {
+//     if (_curPage == 1) {
+//       //展示无数据的布局
+//       state.datas.clear();
+//       changeLoadingState(LoadState.State_Empty);
+//       refreshController.finishRefresh();
+//     } else {
+//       //展示加载完成,没有更多数据了
+//       refreshController.finishLoad(IndicatorResult.noMore);
+//     }
+//   }
+// }
+
+  /// 点击标记全部
+  void markAll() {
+    ToastEngine.show("点击标记全部");
+  }
+}

+ 27 - 0
packages/cpt_main/lib/modules/notification/notification_view_model.g.dart

@@ -0,0 +1,27 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'notification_view_model.dart';
+
+// **************************************************************************
+// RiverpodGenerator
+// **************************************************************************
+
+String _$notificationViewModelHash() =>
+    r'0a11e5ba8ec6d573808dd04e632006978cb7847c';
+
+/// See also [NotificationViewModel].
+@ProviderFor(NotificationViewModel)
+final notificationViewModelProvider = AutoDisposeNotifierProvider<
+    NotificationViewModel, NotificationState>.internal(
+  NotificationViewModel.new,
+  name: r'notificationViewModelProvider',
+  debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
+      ? null
+      : _$notificationViewModelHash,
+  dependencies: null,
+  allTransitiveDependencies: null,
+);
+
+typedef _$NotificationViewModel = AutoDisposeNotifier<NotificationState>;
+// ignore_for_file: type=lint
+// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

BIN
packages/cs_resources/assets/main/notification_item_icon.webp


+ 1 - 0
packages/cs_resources/lib/generated/assets.dart

@@ -74,6 +74,7 @@ class Assets {
   static const String mainMeHouseholdIcon = 'assets/main/me_household_icon.webp';
   static const String mainMeMyPostIcon = 'assets/main/me_my_post_icon.webp';
   static const String mainMeSettingIcon = 'assets/main/me_setting_icon.webp';
+  static const String mainNotificationItemIcon = 'assets/main/notification_item_icon.webp';
   static const String mainPropertyGuide = 'assets/main/property_guide.webp';
   static const String mainRolesGuide = 'assets/main/roles_guide.webp';
   static const String mainTabFeedbackSelected = 'assets/main/tab_feedback_selected.webp';

+ 1 - 0
packages/cs_resources/lib/generated/intl/messages_en.dart

@@ -100,6 +100,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "logout": MessageLookupByLibrary.simpleMessage("Logout"),
         "logout_alert": MessageLookupByLibrary.simpleMessage(
             "Are you sure you want to logout?"),
+        "mark_all": MessageLookupByLibrary.simpleMessage("Mark All"),
         "me": MessageLookupByLibrary.simpleMessage("Me"),
         "mobile_phone": MessageLookupByLibrary.simpleMessage("Mobile Phone"),
         "my_post": MessageLookupByLibrary.simpleMessage("My Posts"),

+ 1 - 0
packages/cs_resources/lib/generated/intl/messages_zh_CN.dart

@@ -85,6 +85,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "login": MessageLookupByLibrary.simpleMessage("登录"),
         "logout": MessageLookupByLibrary.simpleMessage("退出登录"),
         "logout_alert": MessageLookupByLibrary.simpleMessage("你确定要退出登录吗?"),
+        "mark_all": MessageLookupByLibrary.simpleMessage("标记全部"),
         "me": MessageLookupByLibrary.simpleMessage("我的"),
         "mobile_phone": MessageLookupByLibrary.simpleMessage("手机号码"),
         "my_post": MessageLookupByLibrary.simpleMessage("我的发布"),

+ 1 - 0
packages/cs_resources/lib/generated/intl/messages_zh_HK.dart

@@ -85,6 +85,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "login": MessageLookupByLibrary.simpleMessage("登录"),
         "logout": MessageLookupByLibrary.simpleMessage("退出登录"),
         "logout_alert": MessageLookupByLibrary.simpleMessage("你确定要退出登录吗?"),
+        "mark_all": MessageLookupByLibrary.simpleMessage("标记全部"),
         "me": MessageLookupByLibrary.simpleMessage("我的"),
         "mobile_phone": MessageLookupByLibrary.simpleMessage("手机号码"),
         "my_post": MessageLookupByLibrary.simpleMessage("我的发布"),

+ 10 - 0
packages/cs_resources/lib/generated/l10n.dart

@@ -1260,6 +1260,16 @@ class S {
     );
   }
 
+  /// `Mark All`
+  String get mark_all {
+    return Intl.message(
+      'Mark All',
+      name: 'mark_all',
+      desc: '',
+      args: [],
+    );
+  }
+
   /// `Other`
   String get other {
     return Intl.message(

+ 1 - 0
packages/cs_resources/lib/l10n/intl_en.arb

@@ -120,5 +120,6 @@
   "feedback_msg_1": "Help us keep your estate beautiful",
   "feedback_msg_2": "there is something that requires ourattention or if you have an awesome suggestion, we would like to hear from you!",
   "create_new_feedback": "Create New FeedBack",
+  "mark_all": "Mark All",
   "other": "Other"
 }

+ 1 - 0
packages/cs_resources/lib/l10n/intl_zh_CN.arb

@@ -120,5 +120,6 @@
   "feedback_msg_1": "帮助我们保持您的房产美丽",
   "feedback_msg_2": "有些事情需要我们关注,或者如果你有一个很棒的建议,我们想听听你的意见!",
   "create_new_feedback": "创建新的反馈",
+  "mark_all": "标记全部",
   "other": "其他"
 }

+ 1 - 0
packages/cs_resources/lib/l10n/intl_zh_HK.arb

@@ -106,5 +106,6 @@
   "feedback_msg_1": "帮助我们保持您的房产美丽",
   "feedback_msg_2": "有些事情需要我们关注,或者如果你有一个很棒的建议,我们想听听你的意见!",
   "create_new_feedback": "创建新的反馈",
+  "mark_all": "标记全部",
   "other": "其他"
 }

+ 6 - 0
packages/cs_resources/lib/theme/app_colors_theme.dart

@@ -28,6 +28,7 @@ class AppColorsTheme extends ThemeExtension<AppColorsTheme> {
   static const _colorEFF3FF = Color(0xFFEFF3FF);
   static const _colorDFF0FF = Color(0xFFDFF0FF);
   static const _color1B61CA = Color(0X4D1B61CA);
+  static const _color8B96BA = Color(0xFF8B96BA);
 
   //暗色主题的一些自定义颜色值
   static const _darkBlackBg = Color(0xFF0F0F0F);
@@ -64,6 +65,7 @@ class AppColorsTheme extends ThemeExtension<AppColorsTheme> {
   final Color tabTextSelectedDefault; //Tab文本,选中主题蓝,黑暗模式为白色
   final Color tabTextUnSelectedDefault; //Tab文本,未选中 亮色为黑色,黑暗模式为灰色
   final Color tabLightBlueShadow; //Tab的淡蓝色阴影
+  final Color textLightPurple; //文本淡紫色
 
   // 私有的构造函数
   const AppColorsTheme._internal({
@@ -93,6 +95,7 @@ class AppColorsTheme extends ThemeExtension<AppColorsTheme> {
     required this.tabLightBlueShadow,
     required this.tabTextSelectedDefault,
     required this.tabTextUnSelectedDefault,
+    required this.textLightPurple,
   });
 
   // 浅色主题工厂方法
@@ -124,6 +127,7 @@ class AppColorsTheme extends ThemeExtension<AppColorsTheme> {
       tabLightBlueShadow: _color1B61CA,
       tabTextSelectedDefault: _colorPrimary,
       tabTextUnSelectedDefault: Colors.black,
+      textLightPurple: _color8B96BA,
     );
   }
 
@@ -156,6 +160,7 @@ class AppColorsTheme extends ThemeExtension<AppColorsTheme> {
       tabLightBlueShadow: _darkBlackItemLightShadow,
       tabTextSelectedDefault: Colors.white,
       tabTextUnSelectedDefault: _darkBlackItemLightMost,
+      textLightPurple: Colors.white,
     );
   }
 
@@ -197,6 +202,7 @@ class AppColorsTheme extends ThemeExtension<AppColorsTheme> {
       tabTextSelectedDefault: Color.lerp(tabTextSelectedDefault, other.tabTextSelectedDefault, t)!,
       tabLightBlueShadow: Color.lerp(tabLightBlueShadow, other.tabLightBlueShadow, t)!,
       tabTextUnSelectedDefault: Color.lerp(tabTextUnSelectedDefault, other.tabTextUnSelectedDefault, t)!,
+      textLightPurple: Color.lerp(textLightPurple, other.textLightPurple, t)!,
     );
   }
 }