如何实现每日使用8小时后禁用Flutter的CupertinoSwitch组件
解决方案
要实现CupertinoSwitch每日在线满8小时后禁用,核心是准确累计当日在线时长并实时判断是否触发禁用条件,你的原有代码只记录了单次切换时间,没有累计在线时长,且未处理App重启后数据丢失的问题,以下是修正方案:
1. 核心逻辑梳理
- 持久化存储当日累计在线时长(避免重启App丢失数据)
- 在线状态切换时,记录切换时间,计算本次在线时长并累加
- 每日零点自动重置累计时长
- 当累计时长≥8小时时,将CupertinoSwitch的
onChanged设为null(禁用开关)
2. 代码实现步骤
第一步:添加持久化依赖
在pubspec.yaml中添加:
dependencies: shared_preferences: ^2.2.2
第二步:在HomeBloc中管理累计时长状态
修改HomeBloc的状态,加入当日累计在线时长字段:
class HomeState extends Equatable { final DriverStatus driverStatus; final Duration dailyOnlineDuration; // 当日累计在线时长 // ...其他原有字段 const HomeState({ required this.driverStatus, required this.dailyOnlineDuration, // ...其他参数 }); // 复制方法,用于更新状态 HomeState copyWith({ DriverStatus? driverStatus, Duration? dailyOnlineDuration, // ...其他参数 }) { return HomeState( driverStatus: driverStatus ?? this.driverStatus, dailyOnlineDuration: dailyOnlineDuration ?? this.dailyOnlineDuration, // ...其他参数 ); } @override List<Object?> get props => [driverStatus, dailyOnlineDuration]; }
在Bloc中添加初始化和时长更新逻辑:
class HomeBloc extends Bloc<HomeEvent, HomeState> { final SharedPreferences prefs; DateTime? _lastOnlineStartTime; // 记录上次切换到在线状态的时间 HomeBloc(this.prefs) : super(HomeState(driverStatus: InitialDriverStatus(), dailyOnlineDuration: Duration.zero)) { // 初始化时从本地读取当日累计时长 _initDailyDuration(); on<UpdateOnlineStatus>(_onUpdateOnlineStatus); on<UpdateDailyDuration>(_onUpdateDailyDuration); } // 初始化当日累计时长 Future<void> _initDailyDuration() async { final today = DateTime.now(); final savedDate = prefs.getString('daily_duration_date'); final savedSeconds = prefs.getInt('daily_duration_seconds') ?? 0; if (savedDate == '${today.year}-${today.month}-${today.day}') { emit(state.copyWith(dailyOnlineDuration: Duration(seconds: savedSeconds))); } else { // 不是当日,重置时长 await prefs.setString('daily_duration_date', '${today.year}-${today.month}-${today.day}'); await prefs.setInt('daily_duration_seconds', 0); emit(state.copyWith(dailyOnlineDuration: Duration.zero)); } } // 更新在线状态时处理时长计算 Future<void> _onUpdateOnlineStatus(UpdateOnlineStatus event, Emitter<HomeState> emit) async { final now = DateTime.now(); if (event.newStatus is OnlineDriverStatus) { // 切换到在线,记录开始时间 _lastOnlineStartTime = now; } else if (event.newStatus is OfflineDriverStatus && _lastOnlineStartTime != null) { // 切换到离线,计算本次在线时长并累加 final sessionDuration = now.difference(_lastOnlineStartTime!); final newTotalDuration = state.dailyOnlineDuration + sessionDuration; // 持久化到本地 await prefs.setInt('daily_duration_seconds', newTotalDuration.inSeconds); emit(state.copyWith(dailyOnlineDuration: newTotalDuration)); _lastOnlineStartTime = null; } // 更新状态 emit(state.copyWith(driverStatus: event.newStatus)); } // 可选:后台定时更新时长(比如App在前台时每分钟更新一次) Future<void> _onUpdateDailyDuration(UpdateDailyDuration event, Emitter<HomeState> emit) async { if (state.driverStatus is OnlineDriverStatus && _lastOnlineStartTime != null) { final now = DateTime.now(); final sessionDuration = now.difference(_lastOnlineStartTime!); final newTotalDuration = state.dailyOnlineDuration + sessionDuration; emit(state.copyWith(dailyOnlineDuration: newTotalDuration)); _lastOnlineStartTime = now; // 持久化 await prefs.setInt('daily_duration_seconds', newTotalDuration.inSeconds); } } // 原有onStatusChanged方法修改为触发UpdateOnlineStatus事件 void onStatusChanged(DriverStatus newStatus) { add(UpdateOnlineStatus(newStatus: newStatus)); } } // 定义对应的事件 class UpdateOnlineStatus extends HomeEvent { final DriverStatus newStatus; const UpdateOnlineStatus({required this.newStatus}); @override List<Object> get props => [newStatus]; } class UpdateDailyDuration extends HomeEvent { const UpdateDailyDuration(); @override List<Object> get props => []; }
第三步:修改UI中的CupertinoSwitch逻辑
根据Bloc中的dailyOnlineDuration判断是否禁用开关:
child: switch (state.driverStatus) { InitialDriverStatus() => const SizedBox(), LoadingDriverStatus() => const SizedBox(), AccessDeniedDriverStatus() => const SizedBox(), OnTripDriverStatus() => const SizedBox(), OnlineDriverStatus() => CupertinoSwitch( value: true, // 累计时长≥8小时时禁用开关 onChanged: state.dailyOnlineDuration >= const Duration(hours: 8) ? null : (_) async { locator<HomeBloc>().onStatusChanged( const DriverStatus.offline(), ); }, activeColor: const Color.fromARGB(255, 253, 141, 20), ), OfflineDriverStatus() => CupertinoSwitch( value: false, // 累计时长≥8小时时禁用开关 onChanged: state.dailyOnlineDuration >= const Duration(hours: 8) ? null : (_) async { final homeBloc = locator<HomeBloc>(); final locationDatsource = locator<LocationDatasource>(); final locationPermissionGranted = await locationDatsource.getLocationPermissionStatus(); switch (locationPermissionGranted) { case LocationPermission.denied: final permissionResult = await showDialog( context: context, useSafeArea: false, builder: (context) => const LocationPermissionRequestDialog(), ); if (permissionResult == false) { return; } case LocationPermission.deniedForever: showDialog( context: context, useSafeArea: false, builder: (context) => const LocationPermissionDeniedForeverDialog(), ); return; case LocationPermission.whileInUse: context.showSnackBar( message: "Background location updates are not allowed, Please allow this permission in your phone settings for optimal experience.", ); break; case LocationPermission.always: break; } final locationServiceEnabled = await locationDatsource.isLocationServiceEnabled(); if (locationServiceEnabled) { homeBloc.onStatusChanged(const DriverStatus.online()); } else { final serviceEnabled = await locationDatsource.requestLocationService(); if (!serviceEnabled) { return; } } homeBloc.onStatusChanged(const DriverStatus.online()); locator<LocationBloc>().state.mapOrNull( determined: (determined) { locator<HomeBloc>().onLocationUpdated( location: determined.location, forceUpdate: true, ); }, ); }, activeColor: const Color.fromARGB(255, 253, 141, 20), ), },
3. 关键说明
- 持久化存储:用SharedPreferences保存当日累计时长和日期,确保App重启后数据不丢失,且次日自动重置。
- 实时时长计算:切换在线/离线状态时计算单次会话时长并累加,同时可通过定时事件(比如每分钟触发一次)更新前台在线时长,避免切换不及时导致的时长统计误差。
- 禁用开关:当累计时长达到8小时时,将
onChanged设为null,CupertinoSwitch会自动变为禁用状态,用户无法操作。
内容的提问来源于stack exchange,提问作者Tommz
相关产品推荐
相关产品推荐

