From 8501b5ea489fe88b4ef25d64499603dac15f7cc6 Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 3 Sep 2025 17:05:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=BC=B9=E5=87=BA?= =?UTF-8?q?=E6=B0=94=E6=B3=A1=E3=80=81=E6=90=9C=E7=B4=A2=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/src/main/AndroidManifest.xml | 15 ++ lib/app.dart | 22 +-- lib/base/app_permission.dart | 128 ++++++++++++++++ lib/base/base_controller.dart | 19 +++ lib/common/widgets/custom_dialog_widget.dart | 110 ++++++++++++++ lib/flavors.dart | 2 +- lib/routes/app_pages.dart | 7 + lib/routes/app_routes.dart | 1 + .../searchDevice/search_device_binding.dart | 9 ++ .../search_device_controller.dart | 22 +++ .../searchDevice/search_device_view.dart | 42 ++++++ .../search_device_rotating_icon_widget.dart | 100 +++++++++++++ lib/views/home/home_view.dart | 37 +++-- lib/views/login/login_controller.dart | 139 ++++-------------- lib/views/messages/messages_view.dart | 43 ++++-- 15 files changed, 545 insertions(+), 151 deletions(-) create mode 100644 lib/common/widgets/custom_dialog_widget.dart create mode 100644 lib/views/device/searchDevice/search_device_binding.dart create mode 100644 lib/views/device/searchDevice/search_device_controller.dart create mode 100644 lib/views/device/searchDevice/search_device_view.dart create mode 100644 lib/views/device/searchDevice/widget/search_device_rotating_icon_widget.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 533f5bb..7665c8b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -11,6 +11,21 @@ + + + + + + + + + + + + + + { builder: (_, child) { return GetMaterialApp( theme: ThemeData( - scaffoldBackgroundColor: Colors.white, - brightness: Brightness.light, - textSelectionTheme: TextSelectionThemeData( - cursorColor: Colors.blue.shade200, - selectionColor: Colors.blue.shade100, - selectionHandleColor: Colors.blue.shade300, - ), - dialogBackgroundColor: Colors.white, - ), + scaffoldBackgroundColor: Colors.white, + brightness: Brightness.light, + textSelectionTheme: TextSelectionThemeData( + cursorColor: Colors.blue.shade200, + selectionColor: Colors.blue.shade100, + selectionHandleColor: Colors.blue.shade300, + ), + dialogBackgroundColor: Colors.white, + appBarTheme: AppBarTheme( + backgroundColor: Colors.white, + )), // 必须配置以下三个本地化代理 localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, // 提供Material组件本地化字符串 diff --git a/lib/base/app_permission.dart b/lib/base/app_permission.dart index 2d02f33..fd117c4 100644 --- a/lib/base/app_permission.dart +++ b/lib/base/app_permission.dart @@ -46,4 +46,132 @@ class AppPermission { return false; } + + // 请求蓝牙权限(Android 12及以上需要) + static Future requestBluetoothPermissions() async { + try { + // Android 12及以上需要的蓝牙权限 + List bluetoothPermissions = [ + Permission.bluetoothScan, + Permission.bluetoothConnect, + Permission.bluetoothAdvertise, + ]; + + // Android 12以下需要的位置权限 + List locationPermissions = [ + Permission.location, + Permission.locationWhenInUse, + ]; + + bool bluetoothGranted = true; + bool locationGranted = true; + + // 检查并请求蓝牙权限(Android 12+) + Map bluetoothStatuses = await bluetoothPermissions.request(); + for (var status in bluetoothStatuses.values) { + if (status != PermissionStatus.granted) { + bluetoothGranted = false; + break; + } + } + + // 检查并请求位置权限(Android 12以下或蓝牙扫描需要) + Map locationStatuses = await locationPermissions.request(); + for (var status in locationStatuses.values) { + if (status != PermissionStatus.granted) { + locationGranted = false; + break; + } + } + + bool hasPermission = bluetoothGranted && locationGranted; + + if (hasPermission) { + print("蓝牙权限已授予"); + } else { + print("蓝牙权限被拒绝"); + } + + return hasPermission; + + } catch (e) { + print("请求蓝牙权限失败: $e"); + return false; + } + } + + // 检查蓝牙扫描权限 + static Future checkBluetoothScanPermission() async { + try { + var status = await Permission.bluetoothScan.status; + return status == PermissionStatus.granted; + } catch (e) { + // 如果权限不存在(Android 12以下),检查位置权限 + return await checkLocationPermission(); + } + } + + // 检查蓝牙连接权限 + static Future checkBluetoothConnectPermission() async { + try { + var status = await Permission.bluetoothConnect.status; + return status == PermissionStatus.granted; + } catch (e) { + // 如果权限不存在(Android 12以下),默认返回true + print("蓝牙连接权限检查失败,可能是Android 12以下版本: $e"); + return true; + } + } + + // 检查蓝牙广播权限 + static Future checkBluetoothAdvertisePermission() async { + try { + var status = await Permission.bluetoothAdvertise.status; + return status == PermissionStatus.granted; + } catch (e) { + // 如果权限不存在(Android 12以下),默认返回true + print("蓝牙广播权限检查失败,可能是Android 12以下版本: $e"); + return true; + } + } + + // 检查位置权限(Android 12以下蓝牙扫描需要) + static Future checkLocationPermission() async { + var status = await Permission.location.status; + return status == PermissionStatus.granted; + } + + // 请求位置权限 + static Future requestLocationPermission() async { + try { + final PermissionStatus status = await Permission.location.request(); + + if (status == PermissionStatus.granted) { + print("位置权限已授予"); + return true; + } else if (status == PermissionStatus.denied) { + print("位置权限被拒绝"); + return false; + } else if (status.isPermanentlyDenied) { + print("位置权限被永久拒绝,跳转到设置页面"); + openAppSettings(); + return false; + } + + return false; + } catch (e) { + print("请求位置权限失败: $e"); + return false; + } + } + + // 检查所有蓝牙相关权限 + static Future checkAllBluetoothPermissions() async { + bool scanPermission = await checkBluetoothScanPermission(); + bool connectPermission = await checkBluetoothConnectPermission(); + bool advertisePermission = await checkBluetoothAdvertisePermission(); + bool locationPermission = await checkLocationPermission(); + + return scanPermission && connectPermission && advertisePermission && locationPermission; + } } diff --git a/lib/base/base_controller.dart b/lib/base/base_controller.dart index 6130de9..6fa1bba 100644 --- a/lib/base/base_controller.dart +++ b/lib/base/base_controller.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:starwork_flutter/common/widgets/custom_dialog_widget.dart'; class BaseController extends GetxController { void showToast(String message) { @@ -24,6 +25,24 @@ class BaseController extends GetxController { EasyLoading.showError(message.tr); } + void showCustomDialog({ + required String title, + required Widget content, + required VoidCallback onConfirm, + String? confirmText + }) { + Get.dialog( + CustomDialogWidget( + title: title, + content: content, + onConfirm: onConfirm, + confirmText: confirmText, + ), + barrierDismissible: false, // 点击遮罩是否关闭 + useSafeArea: true, // 推荐保持默认 + ); + } + @override void onClose() { if (EasyLoading.isShow) { diff --git a/lib/common/widgets/custom_dialog_widget.dart b/lib/common/widgets/custom_dialog_widget.dart new file mode 100644 index 0000000..5bb3d25 --- /dev/null +++ b/lib/common/widgets/custom_dialog_widget.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +class CustomDialogWidget extends StatefulWidget { + CustomDialogWidget({ + super.key, + required this.title, + required this.content, + required this.onConfirm, + String? confirmText, + }) : confirmText = confirmText ?? '确认'.tr; + + final String title; + final Widget content; + final VoidCallback onConfirm; + final String confirmText; + + @override + State createState() => _CustomDialogWidgetState(); +} + +class _CustomDialogWidgetState extends State { + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14.r), // 圆角 + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 18.h), + Text( + widget.title, + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 10.h), + widget.content, + SizedBox(height: 10.h), + Container( + decoration: const BoxDecoration( + border: Border( + top: BorderSide( + color: Colors.grey, // 右侧边框颜色 + width: 0.5, // 右侧边框宽度 + ), + ), + ), + child: Row( + children: [ + Expanded( + // 左侧文本占一半 + child: GestureDetector( + onTap: () { + Get.back(); + }, + child: Container( + alignment: Alignment.center, + height: 42.h, + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: Colors.grey, // 右侧边框颜色 + width: 0.5, // 右侧边框宽度 + ), + ), + ), + child: Text( + '取消'.tr, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 14.sp, color: Colors.grey), + ), + ), + ), + ), + Expanded( + // 右侧文本占一半 + child: GestureDetector( + onTap: () { + Get.back(); + widget.onConfirm(); + }, + child: Container( + alignment: Alignment.center, + height: 42.h, + child: Text( + widget.confirmText, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14.sp, + color: Colors.blue, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/flavors.dart b/lib/flavors.dart index e64b804..99d932f 100644 --- a/lib/flavors.dart +++ b/lib/flavors.dart @@ -32,7 +32,7 @@ class F { static String get apiHost { switch (appFlavor) { case Flavor.skyDev: - return 'http://10.0.2.2/api'; + return 'http://192.168.1.136/api'; case Flavor.skyPre: return 'https://loacl.work.star-lock.cn/api'; case Flavor.skyRelease: diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index d05c11f..52444d6 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -1,5 +1,7 @@ import 'package:get/get.dart'; import 'package:starwork_flutter/routes/app_routes.dart'; +import 'package:starwork_flutter/views/device/searchDevice/search_device_binding.dart'; +import 'package:starwork_flutter/views/device/searchDevice/search_device_view.dart'; import 'package:starwork_flutter/views/home/home_binding.dart'; import 'package:starwork_flutter/views/home/home_view.dart'; import 'package:starwork_flutter/views/login/forgotPassword/forgot_password_binding.dart'; @@ -60,5 +62,10 @@ class AppPages { page: () => const SetNewPasswordView(), binding: SetNewPasswordBinding(), ), + GetPage( + name: AppRoutes.searchDevice, + page: () => const SearchDeviceView(), + binding: SearchDeviceBinding(), + ), ]; } diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index c50736b..1994e8b 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -8,4 +8,5 @@ class AppRoutes{ static const String inputVerificationCode = '/inputVerificationCode'; static const String forgotPassword = '/forgotPassword'; static const String setNewPassword = '/setNewPassword'; + static const String searchDevice = '/searchDevice'; } \ No newline at end of file diff --git a/lib/views/device/searchDevice/search_device_binding.dart b/lib/views/device/searchDevice/search_device_binding.dart new file mode 100644 index 0000000..909ffda --- /dev/null +++ b/lib/views/device/searchDevice/search_device_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import 'package:starwork_flutter/views/device/searchDevice/search_device_controller.dart'; + +class SearchDeviceBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => SearchDeviceController()); + } +} diff --git a/lib/views/device/searchDevice/search_device_controller.dart b/lib/views/device/searchDevice/search_device_controller.dart new file mode 100644 index 0000000..16d9a66 --- /dev/null +++ b/lib/views/device/searchDevice/search_device_controller.dart @@ -0,0 +1,22 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +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'; + +class SearchDeviceController extends BaseController { + // 搜索状态管理 + final RxBool _isSearching = false.obs; + + // Getter + bool get isSearching => _isSearching.value; + + @override + void onInit() async { + super.onInit(); + } +} diff --git a/lib/views/device/searchDevice/search_device_view.dart b/lib/views/device/searchDevice/search_device_view.dart new file mode 100644 index 0000000..8ec92f2 --- /dev/null +++ b/lib/views/device/searchDevice/search_device_view.dart @@ -0,0 +1,42 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:starwork_flutter/views/device/searchDevice/search_device_controller.dart'; +import 'package:starwork_flutter/views/device/searchDevice/widget/search_device_rotating_icon_widget.dart'; + +class SearchDeviceView extends GetView { + const SearchDeviceView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Row( + children: [ + Obx(() => Text( + controller.isSearching ? '搜索设备中'.tr : '搜索设备'.tr, + )), + SizedBox( + width: 8.w, + ), + Obx( + () => SearchDeviceRotatingIconWidget( + isRotating: controller.isSearching, + radius: 10.w, + rotationDuration: 1500, + ), + ), + ], + ), + ), + body: Container( + // TODO: 添加设备搜索结果列表 + child: Center( + child: Text('设备搜索页面'), + ), + ), + ); + } +} diff --git a/lib/views/device/searchDevice/widget/search_device_rotating_icon_widget.dart b/lib/views/device/searchDevice/widget/search_device_rotating_icon_widget.dart new file mode 100644 index 0000000..07140f6 --- /dev/null +++ b/lib/views/device/searchDevice/widget/search_device_rotating_icon_widget.dart @@ -0,0 +1,100 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class SearchDeviceRotatingIconWidget extends StatefulWidget { + /// 是否正在旋转 + final bool isRotating; + /// 旋转速度(毫秒) + final int rotationDuration; + /// 图标颜色 + final Color? color; + /// 图标半径 + final double? radius; + + const SearchDeviceRotatingIconWidget({ + super.key, + required this.isRotating, + this.rotationDuration = 1000, + this.color = Colors.grey, + this.radius, + }); + + @override + _SearchDeviceRotatingIconWidgetState createState() => _SearchDeviceRotatingIconWidgetState(); +} + +class _SearchDeviceRotatingIconWidgetState extends State + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: Duration(milliseconds: widget.rotationDuration), + vsync: this, + ); + + // 根据初始状态决定是否开始动画 + if (widget.isRotating) { + _animationController.repeat(); + } + } + + @override + void didUpdateWidget(SearchDeviceRotatingIconWidget oldWidget) { + super.didUpdateWidget(oldWidget); + + // 当isRotating状态发生变化时,控制动画的开始和停止 + if (widget.isRotating != oldWidget.isRotating) { + if (widget.isRotating) { + _startRotation(); + } else { + _stopRotation(); + } + } + + // 当旋转速度发生变化时,更新动画时长 + if (widget.rotationDuration != oldWidget.rotationDuration) { + _animationController.duration = Duration(milliseconds: widget.rotationDuration); + } + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + /// 开始旋转动画 + void _startRotation() { + if (!_animationController.isAnimating) { + _animationController.repeat(); + } + } + + /// 停止旋转动画 + void _stopRotation() { + if (_animationController.isAnimating) { + _animationController.stop(); + } + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _animationController, + builder: (context, child) { + return Transform.rotate( + angle: _animationController.value * 2 * 3.14159, + child: CupertinoActivityIndicator( + radius: widget.radius ?? 10.w, + color: widget.color, + ), + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/views/home/home_view.dart b/lib/views/home/home_view.dart index 5b6ec5d..3432a03 100644 --- a/lib/views/home/home_view.dart +++ b/lib/views/home/home_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:starwork_flutter/base/app_permission.dart'; +import 'package:starwork_flutter/routes/app_routes.dart'; import 'package:super_tooltip/super_tooltip.dart'; import 'package:starwork_flutter/views/home/widget/home_attendance_chart_area_widget.dart'; import 'package:starwork_flutter/views/home/widget/home_carousel_area_widget.dart'; @@ -175,10 +176,15 @@ class HomeView extends GetView { SizedBox( width: 14.w, ), - Icon( - Icons.cancel, - color: const Color(0xFFEE9846), - size: 18.sp, + GestureDetector( + onTap: () { + controller.isOpenNotificationPermission.value = true; + }, + child: Icon( + Icons.cancel, + color: const Color(0xFFEE9846), + size: 18.sp, + ), ) ], ), @@ -263,12 +269,12 @@ class HomeView extends GetView { { 'title': '搜索设备', 'icon': Icons.sensors_rounded, - 'onTap': () => _handleMenuTap('添加设备'), + 'onTap': () => _handleMenuTap(0), }, { 'title': '加入团队', 'icon': Icons.group_add, - 'onTap': () => _handleMenuTap('创建群组'), + 'onTap': () => _handleMenuTap(1), }, ]; @@ -329,7 +335,9 @@ class HomeView extends GetView { size: 18.sp, color: Colors.black87, ), - SizedBox(width: 8.w,), + SizedBox( + width: 8.w, + ), Flexible( child: Text( title, @@ -348,12 +356,13 @@ class HomeView extends GetView { } /// 处理菜单点击事件 - void _handleMenuTap(String menuTitle) { - Get.snackbar( - '提示', - '点击了:$menuTitle', - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 2), - ); + void _handleMenuTap(int menuIndex) { + switch (menuIndex) { + case 0: + Get.toNamed(AppRoutes.searchDevice); + break; + case 1: + break; + } } } diff --git a/lib/views/login/login_controller.dart b/lib/views/login/login_controller.dart index 724a1cb..5d1de43 100644 --- a/lib/views/login/login_controller.dart +++ b/lib/views/login/login_controller.dart @@ -72,127 +72,44 @@ class LoginController extends BaseController { required String content, required VoidCallback onConfirm, }) { - Get.dialog( - Dialog( - backgroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14.r), // 圆角 - ), - child: Column( - mainAxisSize: MainAxisSize.min, + showCustomDialog( + title: title, + content: RichText( + text: TextSpan( children: [ - Padding( - padding: EdgeInsets.only(top: 22.h), - child: Text( - title, - style: TextStyle( - fontSize: 18.sp, - fontWeight: FontWeight.bold, - ), + TextSpan( + text: '请你阅读并同意'.tr, + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey, ), ), - Padding( - padding: EdgeInsets.all(22.w), - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: '请你阅读并同意'.tr, - style: TextStyle( - fontSize: 12.sp, - color: Colors.grey, - ), - ), - TextSpan( - text: '《用户协议》'.tr, - style: TextStyle( - fontSize: 12.sp, - color: Colors.blue, - ), - ), - TextSpan( - text: '和'.tr, - style: TextStyle( - fontSize: 12.sp, - color: Colors.grey, - ), - ), - TextSpan( - text: '《隐私政策》'.tr, - style: TextStyle( - fontSize: 12.sp, - color: Colors.blue, - ), - ), - ], - ), + TextSpan( + text: '《用户协议》'.tr, + style: TextStyle( + fontSize: 12.sp, + color: Colors.blue, ), ), - SizedBox(height: 20.h), - Container( - decoration: const BoxDecoration( - border: Border( - top: BorderSide( - color: Colors.grey, // 右侧边框颜色 - width: 0.5, // 右侧边框宽度 - ), - ), + TextSpan( + text: '和'.tr, + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey, ), - child: Row( - children: [ - Expanded( - // 左侧文本占一半 - child: GestureDetector( - onTap: () { - Get.back(); - }, - child: Container( - alignment: Alignment.center, - height: 42.h, - decoration: const BoxDecoration( - border: Border( - right: BorderSide( - color: Colors.grey, // 右侧边框颜色 - width: 0.5, // 右侧边框宽度 - ), - ), - ), - child: Text( - '取消'.tr, - textAlign: TextAlign.center, - style: TextStyle(fontSize: 14.sp, color: Colors.grey), - ), - ), - ), - ), - Expanded( - // 右侧文本占一半 - child: GestureDetector( - onTap: () { - Get.back(); - onConfirm(); - }, - child: Container( - alignment: Alignment.center, - height: 42.h, - child: Text( - '同意并继续'.tr, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14.sp, - color: Colors.blue, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - ), - ], + ), + TextSpan( + text: '《隐私政策》'.tr, + style: TextStyle( + fontSize: 12.sp, + color: Colors.blue, ), - ) + ), ], ), ), + onConfirm: onConfirm, + confirmText: '同意并继续'.tr, ); } } diff --git a/lib/views/messages/messages_view.dart b/lib/views/messages/messages_view.dart index 9e5989a..b352895 100644 --- a/lib/views/messages/messages_view.dart +++ b/lib/views/messages/messages_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; 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/app_permission.dart'; @@ -201,10 +202,16 @@ class MessagesView extends GetView { SizedBox( width: 14.w, ), - Icon( - Icons.cancel, - color: const Color(0xFFEE9846), - size: 18.sp, + GestureDetector( + onTap: () { + controller.homeController.isOpenNotificationPermission.value = + true; + }, + child: Icon( + Icons.cancel, + color: const Color(0xFFEE9846), + size: 18.sp, + ), ) ], ), @@ -217,25 +224,31 @@ class MessagesView extends GetView { return RefreshIndicator( onRefresh: _onRefresh, // 基础样式配置 - color: const Color(0xFF4A90E2), // 刷新指示器颜色 - backgroundColor: Colors.white, // 背景颜色 - + color: const Color(0xFF4A90E2), + // 刷新指示器颜色 + backgroundColor: Colors.white, + // 背景颜色 + // 控制下拉触发距离的关键属性 - displacement: 60.0, // 刷新指示器距离顶部的距离(默认40.0) - edgeOffset: 0.0, // 边缘偏移量(默认0.0) - + displacement: 60.0, + // 刷新指示器距离顶部的距离(默认40.0) + edgeOffset: 0.0, + // 边缘偏移量(默认0.0) + // 控制下拉幅度的关键属性 - triggerMode: RefreshIndicatorTriggerMode.onEdge, // 触发模式 + triggerMode: RefreshIndicatorTriggerMode.onEdge, + // 触发模式 // RefreshIndicatorTriggerMode.onEdge: 在边缘触发(默认) // RefreshIndicatorTriggerMode.anywhere: 在任何位置都可以触发 - + // 描边宽度 - strokeWidth: 2.5, // 刷新指示器的描边宽度(默认2.0) - + strokeWidth: 2.5, + // 刷新指示器的描边宽度(默认2.0) + // 语义标签(用于无障碍功能) semanticsLabel: '下拉刷新消息列表', semanticsValue: '刷新中...', - + child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics( // 控制滚动物理特性