You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现每日使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 05:44:54