diff --git a/assets/icon/access_authorization.png b/assets/icon/access_authorization.png new file mode 100644 index 0000000..0c9e417 Binary files /dev/null and b/assets/icon/access_authorization.png differ diff --git a/assets/icon/access_management.png b/assets/icon/access_management.png new file mode 100644 index 0000000..0b91783 Binary files /dev/null and b/assets/icon/access_management.png differ diff --git a/assets/icon/announcement.png b/assets/icon/announcement.png new file mode 100644 index 0000000..0eb1231 Binary files /dev/null and b/assets/icon/announcement.png differ diff --git a/assets/icon/approval_record.png b/assets/icon/approval_record.png new file mode 100644 index 0000000..37818b5 Binary files /dev/null and b/assets/icon/approval_record.png differ diff --git a/assets/icon/attendance_setting.png b/assets/icon/attendance_setting.png new file mode 100644 index 0000000..509922a Binary files /dev/null and b/assets/icon/attendance_setting.png differ diff --git a/assets/icon/attendance_statistics.png b/assets/icon/attendance_statistics.png new file mode 100644 index 0000000..816da54 Binary files /dev/null and b/assets/icon/attendance_statistics.png differ diff --git a/assets/icon/bar/home.png b/assets/icon/bar/home.png new file mode 100644 index 0000000..9edc026 Binary files /dev/null and b/assets/icon/bar/home.png differ diff --git a/assets/icon/bar/home_selected.png b/assets/icon/bar/home_selected.png new file mode 100644 index 0000000..befc618 Binary files /dev/null and b/assets/icon/bar/home_selected.png differ diff --git a/assets/icon/bar/mine.png b/assets/icon/bar/mine.png new file mode 100644 index 0000000..e0152eb Binary files /dev/null and b/assets/icon/bar/mine.png differ diff --git a/assets/icon/bar/mine_selected.png b/assets/icon/bar/mine_selected.png new file mode 100644 index 0000000..d9e05bd Binary files /dev/null and b/assets/icon/bar/mine_selected.png differ diff --git a/assets/icon/bar/notification.png b/assets/icon/bar/notification.png new file mode 100644 index 0000000..6558aed Binary files /dev/null and b/assets/icon/bar/notification.png differ diff --git a/assets/icon/bar/notification_selected.png b/assets/icon/bar/notification_selected.png new file mode 100644 index 0000000..fa11338 Binary files /dev/null and b/assets/icon/bar/notification_selected.png differ diff --git a/assets/icon/broadcast.png b/assets/icon/broadcast.png new file mode 100644 index 0000000..11576cb Binary files /dev/null and b/assets/icon/broadcast.png differ diff --git a/assets/icon/business_trip.png b/assets/icon/business_trip.png new file mode 100644 index 0000000..83c6b6c Binary files /dev/null and b/assets/icon/business_trip.png differ diff --git a/assets/icon/call_relationship.png b/assets/icon/call_relationship.png new file mode 100644 index 0000000..51a0d83 Binary files /dev/null and b/assets/icon/call_relationship.png differ diff --git a/assets/icon/call_reminder.png b/assets/icon/call_reminder.png new file mode 100644 index 0000000..5d91a07 Binary files /dev/null and b/assets/icon/call_reminder.png differ diff --git a/assets/icon/device_management.png b/assets/icon/device_management.png new file mode 100644 index 0000000..cd57bbc Binary files /dev/null and b/assets/icon/device_management.png differ diff --git a/assets/icon/go_out.png b/assets/icon/go_out.png new file mode 100644 index 0000000..7b4e8f9 Binary files /dev/null and b/assets/icon/go_out.png differ diff --git a/assets/icon/icon_one_key_door_key.png b/assets/icon/icon_one_key_door_key.png index 0488296..03948c3 100644 Binary files a/assets/icon/icon_one_key_door_key.png and b/assets/icon/icon_one_key_door_key.png differ diff --git a/assets/icon/icon_table_menu.png b/assets/icon/icon_table_menu.png new file mode 100644 index 0000000..95c72d9 Binary files /dev/null and b/assets/icon/icon_table_menu.png differ diff --git a/assets/icon/info_publish.png b/assets/icon/info_publish.png new file mode 100644 index 0000000..c25aca9 Binary files /dev/null and b/assets/icon/info_publish.png differ diff --git a/assets/icon/initiate_approval.png b/assets/icon/initiate_approval.png new file mode 100644 index 0000000..fb3f158 Binary files /dev/null and b/assets/icon/initiate_approval.png differ diff --git a/assets/icon/intelligent_analysis.png b/assets/icon/intelligent_analysis.png new file mode 100644 index 0000000..5fb5897 Binary files /dev/null and b/assets/icon/intelligent_analysis.png differ diff --git a/assets/icon/intelligent_inspection.png b/assets/icon/intelligent_inspection.png new file mode 100644 index 0000000..2db41e4 Binary files /dev/null and b/assets/icon/intelligent_inspection.png differ diff --git a/assets/icon/intercom_device.png b/assets/icon/intercom_device.png new file mode 100644 index 0000000..60cced3 Binary files /dev/null and b/assets/icon/intercom_device.png differ diff --git a/assets/icon/leave_request.png b/assets/icon/leave_request.png new file mode 100644 index 0000000..3c29cc2 Binary files /dev/null and b/assets/icon/leave_request.png differ diff --git a/assets/icon/make_up_card.png b/assets/icon/make_up_card.png new file mode 100644 index 0000000..0b5fa79 Binary files /dev/null and b/assets/icon/make_up_card.png differ diff --git a/assets/icon/mobile_checkin.png b/assets/icon/mobile_checkin.png new file mode 100644 index 0000000..7f4ee60 Binary files /dev/null and b/assets/icon/mobile_checkin.png differ diff --git a/assets/icon/my_attendance.png b/assets/icon/my_attendance.png new file mode 100644 index 0000000..ed2d5ce Binary files /dev/null and b/assets/icon/my_attendance.png differ diff --git a/assets/icon/my_visitor.png b/assets/icon/my_visitor.png new file mode 100644 index 0000000..88bc8d2 Binary files /dev/null and b/assets/icon/my_visitor.png differ diff --git a/assets/icon/one_click_open_door.png b/assets/icon/one_click_open_door.png new file mode 100644 index 0000000..5198fd5 Binary files /dev/null and b/assets/icon/one_click_open_door.png differ diff --git a/assets/icon/password_open_door.png b/assets/icon/password_open_door.png new file mode 100644 index 0000000..e9ef96d Binary files /dev/null and b/assets/icon/password_open_door.png differ diff --git a/assets/icon/person_capture.png b/assets/icon/person_capture.png new file mode 100644 index 0000000..cbf74cf Binary files /dev/null and b/assets/icon/person_capture.png differ diff --git a/assets/icon/personnel_management.png b/assets/icon/personnel_management.png new file mode 100644 index 0000000..a244721 Binary files /dev/null and b/assets/icon/personnel_management.png differ diff --git a/assets/icon/team_qrcode.png b/assets/icon/team_qrcode.png new file mode 100644 index 0000000..90b5026 Binary files /dev/null and b/assets/icon/team_qrcode.png differ diff --git a/assets/icon/traffic_record.png b/assets/icon/traffic_record.png new file mode 100644 index 0000000..2e76d85 Binary files /dev/null and b/assets/icon/traffic_record.png differ diff --git a/assets/icon/vehicle_management.png b/assets/icon/vehicle_management.png new file mode 100644 index 0000000..32b22a4 Binary files /dev/null and b/assets/icon/vehicle_management.png differ diff --git a/assets/icon/video_center.png b/assets/icon/video_center.png new file mode 100644 index 0000000..3aeec87 Binary files /dev/null and b/assets/icon/video_center.png differ diff --git a/assets/icon/video_search.png b/assets/icon/video_search.png new file mode 100644 index 0000000..8f6ffb3 Binary files /dev/null and b/assets/icon/video_search.png differ diff --git a/assets/icon/visitor_invitation.png b/assets/icon/visitor_invitation.png new file mode 100644 index 0000000..35617bb Binary files /dev/null and b/assets/icon/visitor_invitation.png differ diff --git a/assets/icon/visitor_management.png b/assets/icon/visitor_management.png new file mode 100644 index 0000000..1349f0b Binary files /dev/null and b/assets/icon/visitor_management.png differ diff --git a/lib/common/constant/app_images.dart b/lib/common/constant/app_images.dart index c464f2d..2a9e24e 100644 --- a/lib/common/constant/app_images.dart +++ b/lib/common/constant/app_images.dart @@ -3,4 +3,61 @@ class AppImages{ static const String iconOneKeyDoor = 'assets/icon/icon_one_key_door.png'; static const String iconOneKeyDoorKey = 'assets/icon/icon_one_key_door_key.png'; static const String bgOneKeyDoor = 'assets/images/bg_one_key_door.png'; + static const String mockImage = 'assets/images/mockImage.jpg'; + + // 视频类图标 + static const String iconVideoCenter = 'assets/icon/video_center.png'; + static const String iconIntelligentInspection = 'assets/icon/intelligent_inspection.png'; + static const String iconVideoSearch = 'assets/icon/video_search.png'; + static const String iconIntelligentAnalysis = 'assets/icon/intelligent_analysis.png'; + static const String iconPersonCapture = 'assets/icon/person_capture.png'; + static const String iconVehicleManagement = 'assets/icon/vehicle_management.png'; + + // 人员通行类图标 + static const String iconAccessManagement = 'assets/icon/access_management.png'; + static const String iconAccessAuthorization = 'assets/icon/access_authorization.png'; + static const String iconOneClickOpenDoor = 'assets/icon/one_click_open_door.png'; + static const String iconPasswordOpenDoor = 'assets/icon/password_open_door.png'; + static const String iconTrafficRecord = 'assets/icon/traffic_record.png'; + static const String iconVisitorManagement = 'assets/icon/visitor_management.png'; + static const String iconMyVisitor = 'assets/icon/my_visitor.png'; + static const String iconVisitorInvitation = 'assets/icon/visitor_invitation.png'; + + // 考勤类图标 + static const String iconAttendanceSetting = 'assets/icon/attendance_setting.png'; + static const String iconAttendanceStatistics = 'assets/icon/attendance_statistics.png'; + static const String iconMyAttendance = 'assets/icon/my_attendance.png'; + static const String iconMobileCheckin = 'assets/icon/mobile_checkin.png'; + static const String iconLeaveRequest = 'assets/icon/leave_request.png'; + static const String iconMakeUpCard = 'assets/icon/make_up_card.png'; + static const String iconBusinessTrip = 'assets/icon/business_trip.png'; + static const String iconGoOut = 'assets/icon/go_out.png'; + + // 审批类图标 + static const String iconInitiateApproval = 'assets/icon/initiate_approval.png'; + static const String iconApprovalRecord = 'assets/icon/approval_record.png'; + + // 可视对讲类图标 + static const String iconIntercomDevice = 'assets/icon/intercom_device.png'; + static const String iconCallRelationship = 'assets/icon/call_relationship.png'; + static const String iconCallReminder = 'assets/icon/call_reminder.png'; + + // 信息发布类图标 + static const String iconBroadcast = 'assets/icon/broadcast.png'; + static const String iconAnnouncement = 'assets/icon/announcement.png'; + static const String iconInfoPublish = 'assets/icon/info_publish.png'; + + // 基础应用类图标 + static const String iconPersonnelManagement = 'assets/icon/personnel_management.png'; + static const String iconTeamQrcode = 'assets/icon/team_qrcode.png'; + static const String iconDeviceManagement = 'assets/icon/device_management.png'; + static const String iconTableMenu = 'assets/icon/icon_table_menu.png'; + + // 底部导航栏图标 + static const String iconHome = 'assets/icon/bar/home.png'; + static const String iconHomeSelected = 'assets/icon/bar/home_selected.png'; + static const String iconNotification = 'assets/icon/bar/notification.png'; + static const String iconNotificationSelected = 'assets/icon/bar/notification_selected.png'; + static const String iconMine = 'assets/icon/bar/mine.png'; + static const String iconMineSelected = 'assets/icon/bar/mine_selected.png'; } \ No newline at end of file diff --git a/lib/views/home/home_controller.dart b/lib/views/home/home_controller.dart index 69c0622..a5b270a 100644 --- a/lib/views/home/home_controller.dart +++ b/lib/views/home/home_controller.dart @@ -4,43 +4,89 @@ import 'package:get/get.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:starwork_flutter/base/app_permission.dart'; import 'package:starwork_flutter/base/base_controller.dart'; +import 'package:starwork_flutter/views/main/main_controller.dart'; class HomeController extends BaseController { + + final mainController = Get.find(); + final isOpenNotificationPermission = false.obs; + // 页面加载状态 + final isLoading = true.obs; + var carouselCurrentIndex = 0.obs; - + // 渐变颜色列表 final List gradientColors = [ - const Color(0xFFBFCBEF), // #bfcbef - const Color(0xFFECBE9B), // 原来的颜色 - const Color(0xFFD5F3D5), // 清新绿色 - const Color(0xFFFFB6C1), // 温柔粉色 + const Color(0xFFBFCBEF), + const Color(0xFFECBE9B), ]; - + // 当前渐变颜色 var currentGradientColor = const Color(0xFFBFCBEF).obs; @override void onInit() async { super.onInit(); - isOpenNotificationPermission.value = await AppPermission.checkPermission( - permission: Permission.notification, - ); + + // 模拟初始化加载 + await _initializeData(); // 监听轮播图切换,更新渐变颜色 carouselCurrentIndex.listen((index) { updateGradientColor(index); }); } - + + // 初始化数据 + Future _initializeData() async { + // 模拟加载延迟(2秒) + await Future.delayed(const Duration(seconds: 2)); + + // 检查通知权限 + isOpenNotificationPermission.value = await AppPermission.checkPermission( + permission: Permission.notification, + ); + + // 加载完成,隐藏骨架屏 + isLoading.value = false; + } + // 根据轮播图索引更新渐变颜色 void updateGradientColor(int index) { if (index < gradientColors.length) { currentGradientColor.value = gradientColors[index]; } else { // 如果索引超出颜色数量,使用模运算轮环 - currentGradientColor.value = gradientColors[index % gradientColors.length]; + currentGradientColor.value = + gradientColors[index % gradientColors.length]; } } + + // 首页刷新方法 + Future refreshHome() async { + // 显示加载状态(可选) + // isLoading.value = true; + + // 模拟网络请求延迟 + await Future.delayed(const Duration(seconds: 1)); + + // 这里可以添加实际的刷新逻辑,比如: + // 1. 重新获取轮播图数据 + // 2. 刷新统计数据 + // 3. 更新功能列表 + // 4. 刷新考勤图表数据 + // 5. 更新门禁列表 + + // 重新检查通知权限 + isOpenNotificationPermission.value = await AppPermission.checkPermission( + permission: Permission.notification, + ); + + // 隐藏加载状态 + // isLoading.value = false; + + print('首页数据刷新完成'); + } } diff --git a/lib/views/home/home_view.dart b/lib/views/home/home_view.dart index e7e120b..d013fa2 100644 --- a/lib/views/home/home_view.dart +++ b/lib/views/home/home_view.dart @@ -1,4 +1,3 @@ -import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; @@ -18,9 +17,6 @@ import 'home_controller.dart'; class HomeView extends GetView { const HomeView({super.key}); - static final GlobalKey _scaffoldKey = - GlobalKey(); - @override Widget build(BuildContext context) { return Obx( @@ -36,68 +32,26 @@ class HomeView extends GetView { stops: const [0.1, 1.0], // 第一个颜色到10%,然后第二个颜色开始直到100% ), ), - child: Scaffold( - key: _scaffoldKey, - backgroundColor: Colors.transparent, - body: SafeArea( - child: Container( - width: 1.sw, - padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 4.h), - child: Column( - children: [ - // 固定的上半部分 - _buildPageHead(context), - SizedBox( - height: 10.h, - ), - _buildSystemNotificationPermissionRow(), + child: SafeArea( + child: Container( + width: 1.sw, + padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 4.h), + child: Column( + children: [ + // 固定的上半部分 + _buildPageHead(context), + SizedBox( + height: 10.h, + ), + _buildSystemNotificationPermissionRow(), - // 可滚动的下半部分 - Expanded( - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - HomeCarouselAreaWidget( - carouselCurrentIndex: - controller.carouselCurrentIndex, - ), - HomeTeamNoticeRowWidget(), - HomeStatisticsRowWidget( - personCount: 12, - deviceCount: 1, - ), - SizedBox(height: 10.h), - HomeFunctionListAreaWidget(), - SizedBox( - height: 10.h, - ), - HomeOnButtonDoorOpeningWidget( - doorList: const [ - '主门门禁', - '车库门禁', - '后门门禁', - '单元门门禁', - ], - ), - SizedBox( - height: 10.h, - ), - HomeAttendanceChartAreaWidget(), - SizedBox( - height: 10.h, - ), - ], - ), - ), - ), - ], - ), + // 可滚动的下半部分(带下拉刷新) + Expanded( + child: _buildRefreshableContent(), + ), + ], ), ), - drawer: HomeLeftDrawerWidget( - teamList: ['家庭群组', '测试团队1', '测试团队2'], - ), ), ), ); @@ -106,7 +60,8 @@ class HomeView extends GetView { _buildPageHead(BuildContext context) { return GestureDetector( onTap: () { - _scaffoldKey.currentState?.openDrawer(); + // 使用MainController的专用方法 + controller.mainController.openDrawer(); }, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -199,4 +154,68 @@ class HomeView extends GetView { ), ); } + + // 带下拉刷新的内容区域 + Widget _buildRefreshableContent() { + return RefreshIndicator( + onRefresh: _onRefresh, + // 基础样式配置 + color: const Color(0xFF4A90E2), // 刷新指示器颜色 + backgroundColor: Colors.white, // 背景颜色 + + // 控制下拉触发距离的关键属性 + displacement: 60.0, // 刷新指示器距离顶部的距离 + edgeOffset: 0.0, // 边缘偏移量 + + // 控制下拉幅度的关键属性 + triggerMode: RefreshIndicatorTriggerMode.onEdge, // 触发模式 + + // 描边宽度 + strokeWidth: 2.5, // 刷新指示器的描边宽度 + + // 语义标签(用于无障碍功能) + semanticsLabel: '下拉刷新首页内容', + semanticsValue: '刷新中...', + + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics( + // 添加弹性滚动效果 + parent: BouncingScrollPhysics(), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + HomeCarouselAreaWidget( + carouselCurrentIndex: controller.carouselCurrentIndex, + ), + HomeTeamNoticeRowWidget(), + HomeStatisticsRowWidget( + personCount: 12, + deviceCount: 1, + ), + SizedBox(height: 10.h), + HomeFunctionListAreaWidget(), + SizedBox(height: 10.h), + HomeOnButtonDoorOpeningWidget( + doorList: const [ + '主门门禁', + '车库门禁', + '后门门禁', + '单元门门禁', + ], + ), + SizedBox(height: 10.h), + HomeAttendanceChartAreaWidget(), + SizedBox(height: 10.h), + ], + ), + ), + ); + } + + // 处理下拉刷新 + Future _onRefresh() async { + // 调用controller的刷新方法 + await controller.refreshHome(); + } } diff --git a/lib/views/home/widget/home_carousel_area_widget.dart b/lib/views/home/widget/home_carousel_area_widget.dart index 41511f4..fe25149 100644 --- a/lib/views/home/widget/home_carousel_area_widget.dart +++ b/lib/views/home/widget/home_carousel_area_widget.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:starwork_flutter/common/constant/app_images.dart'; class HomeCarouselAreaWidget extends StatefulWidget { const HomeCarouselAreaWidget({ @@ -18,9 +19,9 @@ class HomeCarouselAreaWidget extends StatefulWidget { class _HomeCarouselAreaWidgetState extends State { final List imgList = [ - 'assets/images/mockImage.jpg', - 'assets/images/mockImage.jpg', - 'assets/images/mockImage.jpg', + AppImages.mockImage, + AppImages.mockImage, + AppImages.mockImage, ]; @override diff --git a/lib/views/home/widget/home_function_list_area_widget.dart b/lib/views/home/widget/home_function_list_area_widget.dart index 79dea13..c529d9e 100644 --- a/lib/views/home/widget/home_function_list_area_widget.dart +++ b/lib/views/home/widget/home_function_list_area_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:starwork_flutter/common/constant/app_images.dart'; class HomeFunctionListAreaWidget extends StatefulWidget { HomeFunctionListAreaWidget({super.key}); @@ -15,18 +16,81 @@ class _HomeFunctionListAreaWidgetState extends State with SingleTickerProviderStateMixin { late TabController _tabController; + // 图片缓存Map + final Map _imageCache = {}; + + // 标记是否已经预加载 + bool _isPreloaded = false; + @override void initState() { super.initState(); _tabController = TabController(length: 6, vsync: this); } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // 在这里预加载图片,确保MediaQuery可用 + if (!_isPreloaded) { + _preloadImages(); + _isPreloaded = true; + } + } + @override void dispose() { _tabController.dispose(); super.dispose(); } + // 预加载所有图片资源 + void _preloadImages() { + final List imagePaths = [ + AppImages.iconAccessManagement, + AppImages.iconAccessAuthorization, + AppImages.iconOneClickOpenDoor, + AppImages.iconPasswordOpenDoor, + AppImages.iconTrafficRecord, + AppImages.iconVisitorManagement, + AppImages.iconMyVisitor, + AppImages.iconVisitorInvitation, + AppImages.iconAttendanceSetting, + AppImages.iconAttendanceStatistics, + AppImages.iconMyAttendance, + AppImages.iconMobileCheckin, + AppImages.iconLeaveRequest, + AppImages.iconMakeUpCard, + AppImages.iconBusinessTrip, + AppImages.iconGoOut, + AppImages.iconInitiateApproval, + AppImages.iconApprovalRecord, + AppImages.iconIntercomDevice, + AppImages.iconCallRelationship, + AppImages.iconCallReminder, + AppImages.iconBroadcast, + AppImages.iconAnnouncement, + AppImages.iconInfoPublish, + AppImages.iconPersonnelManagement, + AppImages.iconTeamQrcode, + AppImages.iconDeviceManagement, + AppImages.iconVideoCenter, + AppImages.iconIntelligentInspection, + AppImages.iconVideoSearch, + AppImages.iconIntelligentAnalysis, + AppImages.iconPersonCapture, + ]; + + for (String path in imagePaths) { + _imageCache[path] = AssetImage(path); + // 预缓存图片,使用异步方式避免阻塞 + precacheImage(_imageCache[path]!, context).catchError((error) { + // 处理预加载失败的情况 + debugPrint('预加载图片失败: $path, 错误: $error'); + }); + } + } + @override Widget build(BuildContext context) { return Container( @@ -59,7 +123,7 @@ class _HomeFunctionListAreaWidgetState extends State return Row( children: [ - Expanded( + Flexible( child: TabBar( controller: _tabController, isScrollable: true, @@ -97,10 +161,22 @@ class _HomeFunctionListAreaWidgetState extends State // 处理菜单点击事件 print('菜单被点击'); }, - child: Icon( - Icons.menu, - size: 18.sp, - color: Colors.grey[600], + child: Image( + image: AssetImage(AppImages.iconTableMenu), + width: 20.w, + height: 20.w, + fit: BoxFit.contain, + gaplessPlayback: true, + // 防止闪烁 + filterQuality: FilterQuality.medium, + // 优化过滤质量 + errorBuilder: (context, error, stackTrace) { + return Icon( + Icons.image_not_supported, + size: 26.sp, + color: Colors.grey, + ); + }, ), ), ), @@ -121,27 +197,28 @@ class _HomeFunctionListAreaWidgetState extends State // 每个分类对应的功能列表 final Map>> functionData = { '人员通行': [ - {'icon': Icons.door_front_door, 'title': '门禁管理'}, - {'icon': Icons.people, 'title': '门禁授权'}, - {'icon': Icons.access_time, 'title': '一键开门'}, - {'icon': Icons.card_membership, 'title': '密码开门'}, - {'icon': Icons.face, 'title': '我的访客'}, - {'icon': Icons.fingerprint, 'title': '访客统计'}, - {'icon': Icons.security, 'title': '访客邀约'}, + {'icon': AppImages.iconAccessManagement, 'title': '门禁管理'}, + {'icon': AppImages.iconAccessAuthorization, 'title': '门禁授权'}, + {'icon': AppImages.iconOneClickOpenDoor, 'title': '一键开门'}, + {'icon': AppImages.iconPasswordOpenDoor, 'title': '密码开门'}, + {'icon': AppImages.iconTrafficRecord, 'title': '通行记录'}, + {'icon': AppImages.iconVisitorManagement, 'title': '访客管理'}, + {'icon': AppImages.iconMyVisitor, 'title': '我的访客'}, + {'icon': AppImages.iconVisitorInvitation, 'title': '访客邀约'}, ], '考勤': [ - {'icon': Icons.schedule, 'title': '考勤设置'}, - {'icon': Icons.assignment, 'title': '审批记录'}, - {'icon': Icons.broadcast_on_personal, 'title': '广播'}, - {'icon': Icons.announcement, 'title': '信息发布'}, - {'icon': Icons.bar_chart, 'title': '统计报表'}, - {'icon': Icons.calendar_today, 'title': '排班管理'}, - {'icon': Icons.access_alarm, 'title': '打卡记录'}, - {'icon': Icons.person_add, 'title': '添加常用'}, + {'icon': AppImages.iconAttendanceSetting, 'title': '考勤设置'}, + {'icon': AppImages.iconAttendanceStatistics, 'title': '考勤统计'}, + {'icon': AppImages.iconMyAttendance, 'title': '我的考勤'}, + {'icon': AppImages.iconMobileCheckin, 'title': '手机打卡'}, + {'icon': AppImages.iconLeaveRequest, 'title': '请假'}, + {'icon': AppImages.iconMakeUpCard, 'title': '补卡'}, + {'icon': AppImages.iconBusinessTrip, 'title': '出差'}, + {'icon': AppImages.iconGoOut, 'title': '外出'}, ], '审批': [ - {'icon': Icons.approval, 'title': '待审批'}, - {'icon': Icons.check_circle, 'title': '已审批'}, + {'icon': AppImages.iconInitiateApproval, 'title': '发起审批'}, + {'icon': AppImages.iconApprovalRecord, 'title': '审批记录'}, {'icon': Icons.pending, 'title': '审批中'}, {'icon': Icons.history, 'title': '审批历史'}, {'icon': Icons.rule, 'title': '审批规则'}, @@ -150,34 +227,34 @@ class _HomeFunctionListAreaWidgetState extends State {'icon': Icons.analytics, 'title': '审批统计'}, ], '可视对讲': [ - {'icon': Icons.call, 'title': '对讲设备'}, - {'icon': Icons.call_received, 'title': '呼叫记录'}, + {'icon': AppImages.iconIntercomDevice, 'title': '对讲设备'}, + {'icon': AppImages.iconCallRelationship, 'title': '呼叫关系'}, + {'icon': AppImages.iconCallReminder, 'title': '呼叫提醒'}, {'icon': Icons.video_call, 'title': '视频通话'}, {'icon': Icons.volume_up, 'title': '广播通知'}, {'icon': Icons.settings_phone, 'title': '设备设置'}, {'icon': Icons.group_add, 'title': '群组管理'}, - {'icon': Icons.record_voice_over, 'title': '语音留言'}, {'icon': Icons.emergency, 'title': '紧急呼叫'}, ], '信息发布': [ - {'icon': Icons.campaign, 'title': '发布信息'}, - {'icon': Icons.list_alt, 'title': '信息列表'}, + {'icon': AppImages.iconBroadcast, 'title': '广播'}, + {'icon': AppImages.iconAnnouncement, 'title': '公告'}, + {'icon': AppImages.iconInfoPublish, 'title': '信息发布'}, {'icon': Icons.schedule_send, 'title': '定时发布'}, {'icon': Icons.group, 'title': '目标群体'}, - {'icon': Icons.image, 'title': '多媒体'}, {'icon': Icons.analytics, 'title': '发布统计'}, {'icon': Icons.edit, 'title': '编辑模板'}, {'icon': Icons.history, 'title': '发布历史'}, ], '其他应用': [ - {'icon': Icons.apps, 'title': '应用中心'}, - {'icon': Icons.extension, 'title': '插件管理'}, - {'icon': Icons.download, 'title': '下载中心'}, - {'icon': Icons.update, 'title': '应用更新'}, - {'icon': Icons.settings, 'title': '应用设置'}, - {'icon': Icons.help, 'title': '帮助中心'}, - {'icon': Icons.feedback, 'title': '意见反馈'}, - {'icon': Icons.info, 'title': '关于我们'}, + {'icon': AppImages.iconPersonnelManagement, 'title': '人员管理'}, + {'icon': AppImages.iconTeamQrcode, 'title': '团队二维码'}, + {'icon': AppImages.iconDeviceManagement, 'title': '设备管理'}, + {'icon': AppImages.iconVideoCenter, 'title': '视频中心'}, + {'icon': AppImages.iconIntelligentInspection, 'title': '智能巡检'}, + {'icon': AppImages.iconVideoSearch, 'title': '录像智搜'}, + {'icon': AppImages.iconIntelligentAnalysis, 'title': '智能分析'}, + {'icon': AppImages.iconPersonCapture, 'title': '人员抓拍'}, ], }; @@ -192,7 +269,7 @@ class _HomeFunctionListAreaWidgetState extends State shrinkWrap: true, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, - childAspectRatio: 1.6, + childAspectRatio: 1.4, ), itemCount: currentFunctions.length, itemBuilder: (context, index) { @@ -208,7 +285,7 @@ class _HomeFunctionListAreaWidgetState extends State ); } - Widget _buildFunctionItem({required IconData icon, required String title}) { + Widget _buildFunctionItem({required dynamic icon, required String title}) { return GestureDetector( onTap: () { print('点击了: $title'); @@ -218,18 +295,21 @@ class _HomeFunctionListAreaWidgetState extends State mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, // 重要:让Column最小化 children: [ - Icon( - icon, - size: 20.sp, // 稍微减小图标 - color: Colors.blue.shade600, - ), + // 优化后的图片渲染 + icon is String + ? _buildOptimizedImage(icon) + : Icon( + icon, + size: 26.sp, + color: Colors.blue.shade600, + ), SizedBox(height: 4.h), // 减少间距 Flexible( // 使用Flexible而不是Expanded child: Text( title, style: TextStyle( - fontSize: 12.sp, // 稍微减小字体 + fontSize: 10.sp, // 稍微减小字体 color: Colors.black87, fontWeight: FontWeight.w400, ), @@ -242,4 +322,27 @@ class _HomeFunctionListAreaWidgetState extends State ), ); } + + // 优化的图片组件 + Widget _buildOptimizedImage(String imagePath) { + return RepaintBoundary( + child: Image( + image: _imageCache[imagePath] ?? AssetImage(imagePath), + width: 26.w, + height: 26.w, + fit: BoxFit.contain, + gaplessPlayback: true, + // 防止闪烁 + filterQuality: FilterQuality.medium, + // 优化过滤质量 + errorBuilder: (context, error, stackTrace) { + return Icon( + Icons.image_not_supported, + size: 26.sp, + color: Colors.grey, + ); + }, + ), + ); + } } diff --git a/lib/views/main/main_controller.dart b/lib/views/main/main_controller.dart index aff0db7..576429a 100644 --- a/lib/views/main/main_controller.dart +++ b/lib/views/main/main_controller.dart @@ -1,28 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:starwork_flutter/base/base_controller.dart'; +import 'package:starwork_flutter/common/constant/app_images.dart'; import 'package:starwork_flutter/views/home/home_view.dart'; import 'package:starwork_flutter/views/messages/messages_view.dart'; import 'package:starwork_flutter/views/mine/mine_view.dart'; class MainController extends BaseController { - // 定义底部导航的标题和图标 - final List bottomNavItems = [ - const BottomNavigationBarItem( - icon: Icon(Icons.home_rounded), - label: '首页', - ), - const BottomNavigationBarItem( - icon: Icon(Icons.messenger_outline_rounded), - label: '消息', - ), - const BottomNavigationBarItem( - icon: Icon(Icons.person), - label: '我的', - ), - ]; + GlobalKey scaffoldKey = + GlobalKey(); // 当前选中的索引 var currentIndex = 0.obs; @@ -37,4 +26,56 @@ class MainController extends BaseController { void changeIndex(int index) { currentIndex.value = index; } + + // 定义底部导航的标题和图标 + List get bottomNavItems => [ + BottomNavigationBarItem( + icon: _buildTabIcon(AppImages.iconHome, AppImages.iconHomeSelected, 0), + label: '首页', + ), + BottomNavigationBarItem( + icon: _buildTabIcon(AppImages.iconNotification, AppImages.iconNotificationSelected, 1), + label: '消息', + ), + BottomNavigationBarItem( + icon: _buildTabIcon(AppImages.iconMine, AppImages.iconMineSelected, 2), + label: '我的', + ), + ]; + + // 构建自定义图标组件 + Widget _buildTabIcon(String unselectedIcon, String selectedIcon, int index) { + return Obx(() => Image.asset( + currentIndex.value == index ? selectedIcon : unselectedIcon, + width: 24.w, + height: 24.w, + fit: BoxFit.contain, + )); + } + + // 打开抽屉的方法 + void openDrawer() { + try { + final scaffoldState = scaffoldKey.currentState; + if (scaffoldState != null && scaffoldState.hasDrawer) { + scaffoldState.openDrawer(); + } else { + print('Cannot open drawer: Scaffold state is null or has no drawer'); + } + } catch (e) { + print('Error opening drawer: $e'); + } + } + + // 关闭抽屉的方法 + void closeDrawer() { + try { + final scaffoldState = scaffoldKey.currentState; + if (scaffoldState != null && scaffoldState.isDrawerOpen) { + scaffoldState.closeDrawer(); + } + } catch (e) { + print('Error closing drawer: $e'); + } + } } diff --git a/lib/views/main/main_view.dart b/lib/views/main/main_view.dart index 80406f8..74c25b8 100644 --- a/lib/views/main/main_view.dart +++ b/lib/views/main/main_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:starwork_flutter/views/main/widget/main_left_drawer_widget.dart'; import 'main_controller.dart'; @@ -10,6 +11,7 @@ class MainView extends GetView { @override Widget build(BuildContext context) { return Scaffold( + key: controller.scaffoldKey, // 添加这一行! // 使用 Obx 响应 currentIndex 的变化 body: Obx( () => IndexedStack( @@ -28,6 +30,9 @@ class MainView extends GetView { unselectedFontSize: 12.sp, ), ), + drawer: MainLeftDrawerWidget( + teamList: ['家庭群组', '测试团队1', '测试团队2'], + ), ); } } diff --git a/lib/views/main/widget/main_left_drawer_widget.dart b/lib/views/main/widget/main_left_drawer_widget.dart new file mode 100644 index 0000000..cf84e84 --- /dev/null +++ b/lib/views/main/widget/main_left_drawer_widget.dart @@ -0,0 +1,280 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class MainLeftDrawerWidget extends StatefulWidget { + MainLeftDrawerWidget({ + super.key, + required this.teamList, + this.selectedTeam, + this.onTeamSelected, + }); + + final List teamList; + final String? selectedTeam; + final Function(String)? onTeamSelected; + + @override + State createState() => _MainLeftDrawerWidgetState(); +} + +class _MainLeftDrawerWidgetState extends State { + @override + Widget build(BuildContext context) { + return Drawer( + width: 0.85.sw, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.zero, // 去掉圆角 + ), + child: Container( + color: const Color(0xFFF6F7FB), + child: SafeArea( + child: Column( + children: [ + // 头部标题栏 + Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '我的团队', + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Container( + width: 28.w, + height: 28.w, + decoration: BoxDecoration( + color: Colors.grey[300], + shape: BoxShape.circle, + ), + child: Icon( + Icons.refresh, + size: 18.sp, + color: Colors.grey[600], + ), + ), + ), + ], + ), + ), + + // 团队列表 + Expanded( + child: Container( + margin: EdgeInsets.symmetric(horizontal: 16.w), + child: ListView.builder( + itemCount: widget.teamList.length, + itemBuilder: (context, index) { + final team = widget.teamList[index]; + final isSelected = team == widget.selectedTeam; + + return Container( + margin: EdgeInsets.only(bottom: 8.h), + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration( + color: isSelected + ? const Color(0xFFE3F2FD) // 选中状态:蓝色背景 + : Colors.white, // 未选中状态:白色背景 + borderRadius: BorderRadius.circular(12.r), + border: isSelected + ? Border.all(color: const Color(0xFF2196F3), width: 1) + : Border.all(color: Colors.grey[200]!, width: 1), + ), + child: GestureDetector( + onTap: () { + // 处理团队选择 + if (widget.onTeamSelected != null) { + widget.onTeamSelected!(team); + } + }, + child: Row( + children: [ + // 用户图标 + Container( + width: 40.w, + height: 40.w, + decoration: BoxDecoration( + color: isSelected + ? const Color(0xFF2196F3) + : Colors.grey[400], + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + Icons.person, + color: Colors.white, + size: 24.sp, + ), + ), + SizedBox(width: 12.w), + + // 团队信息 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + team, // 使用teamList中的元素作为团队昵称 + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: isSelected + ? const Color(0xFF2196F3) + : Colors.black, + ), + ), + SizedBox(height: 4.h), + Row( + children: [ + Icon( + isSelected + ? Icons.check_circle + : Icons.check_circle_outline, + size: 14.sp, + color: isSelected + ? const Color(0xFF2196F3) + : Colors.grey[400], + ), + SizedBox(width: 4.w), + Text( + isSelected ? '已选中' : '未选中', + style: TextStyle( + fontSize: 12.sp, + color: isSelected + ? const Color(0xFF2196F3) + : Colors.grey[600], + ), + ), + ], + ), + ], + ), + ), + + // 设置图标 + GestureDetector( + onTap: () { + // 处理设置点击事件 + Navigator.pop(context); + }, + child: Container( + padding: EdgeInsets.all(8.w), + child: Icon( + Icons.settings, + size: 20.sp, + color: isSelected + ? const Color(0xFF2196F3) + : Colors.grey[600], + ), + ), + ), + ], + ), + ), + ); + }, + ), + ), + ), + + // 底部按钮区域 + Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + child: Column( + children: [ + // 创建团队和加入团队按钮 + Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () { + // 处理创建团队 + Navigator.pop(context); + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 12.h), + decoration: BoxDecoration( + border: Border( + right: BorderSide( + color: Colors.grey[300]!, + width: 0.5, + ), + ), + ), + child: Text( + '创建团队', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + color: Colors.black87, + ), + ), + ), + ), + ), + Expanded( + child: GestureDetector( + onTap: () { + // 处理加入团队 + Navigator.pop(context); + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 12.h), + child: Text( + '加入团队', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + color: Colors.black87, + ), + ), + ), + ), + ), + ], + ), + + // 分割线 + Container( + margin: EdgeInsets.symmetric(vertical: 8.h), + height: 0.5, + color: Colors.grey[300], + ), + + // 快捷添加我的设备按钮 + GestureDetector( + onTap: () { + // 处理快捷添加设备 + Navigator.pop(context); + }, + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 12.h), + child: Text( + '快捷添加我的设备', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + color: Colors.black87, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/messages/messages_controller.dart b/lib/views/messages/messages_controller.dart index 950a8dc..4899349 100644 --- a/lib/views/messages/messages_controller.dart +++ b/lib/views/messages/messages_controller.dart @@ -1,5 +1,81 @@ +import 'package:get/get.dart'; import 'package:starwork_flutter/base/base_controller.dart'; +import 'package:starwork_flutter/views/home/home_controller.dart'; +import 'package:starwork_flutter/views/main/main_controller.dart'; +import 'messages_model.dart'; class MessagesController extends BaseController { + final mainController = Get.find(); + final homeController = Get.find(); + + // 响应式消息列表 + final RxList messageList = [].obs; + + @override + void onInit() { + super.onInit(); + _initializeMessages(); + } + + // 初始化消息数据 + void _initializeMessages() { + messageList.value = [ + MessageItem( + type: MessageType.deviceStatus, + title: '设备状态', + subtitle: '[设备在线] DS-K(L40959329)', + time: '25/08/28', + hasRedDot: true, + ), + MessageItem( + type: MessageType.accessControl, + title: '通行权限', + subtitle: '[通行权限] 下发已结束,成功0条,失败1条,成功0条,失败1条,成功0条,失败1条成功0条,失败1条成功0条,失败1条', + time: '25/08/26', + hasRedDot: false, + ), + MessageItem( + type: MessageType.systemNotice, + title: '系统通知', + subtitle: '没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息没有新的消息', + time: '', + hasRedDot: false, + ), + ]; + } + + // 刷新消息数据 + Future refreshMessages() async { + // 模拟网络请求延迟 + await Future.delayed(const Duration(seconds: 1)); + + // 这里可以添加实际的API调用 + // 模拟新数据 + _initializeMessages(); + + print('消息数据刷新完成'); + } + + // 添加新消息 + void addMessage(MessageItem message) { + messageList.insert(0, message); + } + + // 移除消息 + void removeMessage(int index) { + if (index >= 0 && index < messageList.length) { + messageList.removeAt(index); + } + } + + // 清除所有未读标记 + void clearAllUnread() { + for (int i = 0; i < messageList.length; i++) { + if (messageList[i].hasRedDot) { + messageList[i] = messageList[i].copyWith(hasRedDot: false); + } + } + messageList.refresh(); + } } diff --git a/lib/views/messages/messages_model.dart b/lib/views/messages/messages_model.dart new file mode 100644 index 0000000..194eccb --- /dev/null +++ b/lib/views/messages/messages_model.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; + +// 消息类型枚举 +enum MessageType { + deviceStatus, // 设备状态 + accessControl, // 通行权限 + systemNotice, // 系统通知 +} + +// 消息数据模型 +class MessageItem { + final MessageType type; + final String title; + final String subtitle; + final String time; + final bool hasRedDot; + + MessageItem({ + required this.type, + required this.title, + required this.subtitle, + required this.time, + required this.hasRedDot, + }); + + // 根据消息类型获取图标 + IconData get icon { + switch (type) { + case MessageType.deviceStatus: + return Icons.devices; + case MessageType.accessControl: + return Icons.security; + case MessageType.systemNotice: + return Icons.notifications; + } + } + + // 根据消息类型获取图标颜色 + Color get iconColor { + switch (type) { + case MessageType.deviceStatus: + return const Color(0xFF4A90E2); + case MessageType.accessControl: + return const Color(0xFF4A90E2); + case MessageType.systemNotice: + return const Color(0xFFFF8C42); + } + } + + // 根据消息类型获取图标背景色 + Color get iconBgColor { + switch (type) { + case MessageType.deviceStatus: + return const Color(0xFFE8F4FD); + case MessageType.accessControl: + return const Color(0xFFE8F4FD); + case MessageType.systemNotice: + return const Color(0xFFFFF2E8); + } + } + + // 创建副本,用于更新某些属性 + MessageItem copyWith({ + MessageType? type, + String? title, + String? subtitle, + String? time, + bool? hasRedDot, + }) { + return MessageItem( + type: type ?? this.type, + title: title ?? this.title, + subtitle: subtitle ?? this.subtitle, + time: time ?? this.time, + hasRedDot: hasRedDot ?? this.hasRedDot, + ); + } +} \ No newline at end of file diff --git a/lib/views/messages/messages_view.dart b/lib/views/messages/messages_view.dart index 1fdf353..9e5989a 100644 --- a/lib/views/messages/messages_view.dart +++ b/lib/views/messages/messages_view.dart @@ -1,7 +1,11 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:starwork_flutter/base/app_permission.dart'; import 'messages_controller.dart'; +import 'messages_model.dart'; class MessagesView extends GetView { const MessagesView({super.key}); @@ -9,16 +13,400 @@ class MessagesView extends GetView { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('MessagesView'), - centerTitle: true, - ), - body: const Center( - child: Text( - 'MessagesView is working', - style: TextStyle(fontSize: 20), + backgroundColor: const Color(0xFFF6F7FB), + body: SafeArea( + child: Container( + width: 1.sw, + padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 4.h), + child: Column( + children: [ + // 固定的上半部分 + _buildPageHead(context), + _buildSystemNotificationPermissionRow(), + Obx( + () => Visibility( + visible: controller + .homeController.isOpenNotificationPermission.value, + child: SizedBox( + height: 10.h, + ), + ), + ), + // 消息列表 + Expanded( + child: _buildRefreshableMessageList(), + ), + ], + ), ), ), ); } + + _buildPageHead(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // 左侧标题区域 + Expanded( + child: GestureDetector( + onTap: () { + controller.mainController.scaffoldKey.currentState?.openDrawer(); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( + '19104656的互12312联', + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + Icon( + Icons.arrow_right_rounded, + size: 22.sp, + ), + ], + ), + ), + ), + // 右侧功能按钮区域 + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.min, + children: [ + // 清除未读按钮 + Flexible( + child: GestureDetector( + onTap: () { + // 处理清除未读点击事件 + controller.clearAllUnread(); + print('清除未读被点击'); + }, + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 6.w, // 减小内边距 + vertical: 3.h, + ), + decoration: BoxDecoration( + color: const Color(0xFFF0F0F0), + borderRadius: BorderRadius.circular(12.r), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( + '清除未读', + style: TextStyle( + fontSize: 12.sp, // 减小字体 + color: Colors.black54, + fontWeight: FontWeight.w400, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + ], + ), + ), + ), + ), + // 分隔线 + Container( + height: 16.h, // 减小高度 + width: 1.w, + color: Colors.grey.withOpacity(0.3), + ), + // 通知设置按钮 + Flexible( + child: GestureDetector( + onTap: () { + // 处理通知设置点击事件 + print('通知设置被点击'); + }, + child: Text( + '通知设置', + style: TextStyle( + fontSize: 14.sp, // 减小字体 + color: Colors.black, + fontWeight: FontWeight.w400, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + ), + ], + ), + ), + ], + ); + } + + _buildSystemNotificationPermissionRow() { + return Obx( + () => Visibility( + visible: !controller.homeController.isOpenNotificationPermission.value, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.h), + decoration: BoxDecoration( + color: const Color(0xFFFEF2E5), + borderRadius: BorderRadius.all( + Radius.circular(8.r), + ), + ), + margin: EdgeInsets.symmetric(vertical: 8.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '系统通知未开启,报警消息无法通知'.tr, + style: TextStyle( + color: const Color(0xFFEE9846), + fontSize: 12.sp, + ), + ), + const Spacer(), + GestureDetector( + onTap: () async { + controller.homeController.isOpenNotificationPermission.value = + await AppPermission.requestNotificationPermission(); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 4.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(4.r), + ), + ), + child: Text( + '去开启'.tr, + style: TextStyle( + color: const Color(0xFFEE9846), + fontSize: 12.sp, + ), + ), + ), + ), + SizedBox( + width: 14.w, + ), + Icon( + Icons.cancel, + color: const Color(0xFFEE9846), + size: 18.sp, + ) + ], + ), + ), + ), + ); + } + + _buildRefreshableMessageList() { + return RefreshIndicator( + onRefresh: _onRefresh, + // 基础样式配置 + color: const Color(0xFF4A90E2), // 刷新指示器颜色 + backgroundColor: Colors.white, // 背景颜色 + + // 控制下拉触发距离的关键属性 + displacement: 60.0, // 刷新指示器距离顶部的距离(默认40.0) + edgeOffset: 0.0, // 边缘偏移量(默认0.0) + + // 控制下拉幅度的关键属性 + triggerMode: RefreshIndicatorTriggerMode.onEdge, // 触发模式 + // RefreshIndicatorTriggerMode.onEdge: 在边缘触发(默认) + // RefreshIndicatorTriggerMode.anywhere: 在任何位置都可以触发 + + // 描边宽度 + strokeWidth: 2.5, // 刷新指示器的描边宽度(默认2.0) + + // 语义标签(用于无障碍功能) + semanticsLabel: '下拉刷新消息列表', + semanticsValue: '刷新中...', + + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics( + // 控制滚动物理特性 + parent: BouncingScrollPhysics(), // 添加弹性滚动效果 + ), + child: Column( + children: [ + _buildMessageList(), + SizedBox(height: 16.h), + _buildMessageTip(), + ], + ), + ), + ); + } + + Future _onRefresh() async { + // 调用controller的刷新方法 + await controller.refreshMessages(); + } + + _buildMessageList() { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8.r), + ), + child: Obx( + () => Column( + children: [ + // 动态生成消息列表项 + ...controller.messageList.asMap().entries.map((entry) { + int index = entry.key; + MessageItem message = entry.value; + + return Column( + children: [ + _buildMessageItem( + message: message, + onTap: () => _onMessageTap(index, message), + ), + // 如果不是最后一项,显示分隔线 + if (index < controller.messageList.length - 1) + _buildDivider(), + ], + ); + }).toList(), + ], + ), + ), + ); + } + + _buildMessageTip() { + return Container( + padding: EdgeInsets.symmetric(vertical: 10.h), + child: Text( + '只展示最近7天的消息', + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey[500], + fontWeight: FontWeight.w400, + ), + ), + ); + } + + _buildMessageItem({ + required MessageItem message, + VoidCallback? onTap, + }) { + return GestureDetector( + onTap: onTap ?? + () { + // 默认点击事件 + print('${message.title} 被点击'); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h), + child: Row( + children: [ + // 左侧图标 + Stack( + children: [ + Container( + width: 40.w, + height: 40.w, + decoration: BoxDecoration( + color: message.iconBgColor, + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + message.icon, + color: message.iconColor, + size: 20.sp, + ), + ), + if (message.hasRedDot) + Positioned( + top: 0, + right: 0, + child: Container( + width: 8.w, + height: 8.w, + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + ), + ), + ], + ), + SizedBox(width: 12.w), + // 中间内容 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + message.title, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + SizedBox(height: 4.h), + Text( + message.subtitle, + style: TextStyle( + fontSize: 14.sp, + color: Colors.grey[600], + fontWeight: FontWeight.w400, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + // 右侧时间 + if (message.time.isNotEmpty) + Text( + message.time, + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey[400], + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ); + } + + // 处理消息项点击事件 + void _onMessageTap(int index, MessageItem message) { + print('${message.title} 被点击,索引: $index'); + // 这里可以添加具体的业务逻辑,比如跳转到详情页面 + // 如果有未读标记,可以标记为已读 + if (message.hasRedDot) { + // 可以在这里调用controller的方法标记为已读 + } + } + + _buildDivider() { + return Container( + margin: EdgeInsets.symmetric(horizontal: 16.w), + height: 1.h, + color: Colors.grey[200], + ); + } } diff --git a/pubspec.yaml b/pubspec.yaml index eb8960a..da4ca4f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,4 +51,5 @@ flutter: - assets/images/ - assets/logo/ - assets/icon/ + - assets/icon/bar/