|
|
@@ -0,0 +1,228 @@
|
|
|
+import 'package:flutter/material.dart';
|
|
|
+import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
|
+import 'package:location/resource/colors.gen.dart';
|
|
|
+import 'package:location/utils/common_expand.dart';
|
|
|
+import 'dart:async';
|
|
|
+
|
|
|
+import '../../utils/mmkv_util.dart';
|
|
|
+
|
|
|
+final String MemberDiscountCountdownTimeCount = 'key_member_discount_countdown_time_count';
|
|
|
+
|
|
|
+class MemberDiscountCountdownWidget extends StatefulWidget {
|
|
|
+ final Duration duration; // 倒计时总时长(默认1小时)
|
|
|
+ final TextStyle? textStyle;
|
|
|
+ final Duration animationDuration;
|
|
|
+ final Widget? placeholder;
|
|
|
+ final Widget? expiredWidget; // 已过期时显示的组件
|
|
|
+ final VoidCallback? onExpired; // 倒计时结束回调
|
|
|
+
|
|
|
+ const MemberDiscountCountdownWidget({
|
|
|
+ Key? key,
|
|
|
+ this.duration = const Duration(hours: 1),
|
|
|
+ this.textStyle,
|
|
|
+ this.animationDuration = const Duration(milliseconds: 500),
|
|
|
+ this.placeholder,
|
|
|
+ this.expiredWidget,
|
|
|
+ this.onExpired,
|
|
|
+ }) : super(key: key);
|
|
|
+
|
|
|
+ @override
|
|
|
+ _MemberDiscountCountdownWidgetState createState() => _MemberDiscountCountdownWidgetState();
|
|
|
+}
|
|
|
+
|
|
|
+class _MemberDiscountCountdownWidgetState extends State<MemberDiscountCountdownWidget> with SingleTickerProviderStateMixin {
|
|
|
+ // 1. 将_timer改为可空类型,移除late
|
|
|
+ Timer? _timer;
|
|
|
+ bool _isVisible = true;
|
|
|
+ bool _isExpired = false;
|
|
|
+ DateTime? _startTime;
|
|
|
+ Duration? _remainingDuration;
|
|
|
+ bool _isLoading = true;
|
|
|
+
|
|
|
+ @override
|
|
|
+ void initState() {
|
|
|
+ super.initState();
|
|
|
+ _loadStartTime();
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<void> _loadStartTime() async {
|
|
|
+ try {
|
|
|
+ final savedTime = KVUtil.getInt(MemberDiscountCountdownTimeCount, 0);
|
|
|
+ print("savetimefdifjd---${savedTime}");
|
|
|
+ if (savedTime != null && savedTime > 0) {
|
|
|
+ _startTime = DateTime.fromMillisecondsSinceEpoch(savedTime);
|
|
|
+ _updateRemainingTime();
|
|
|
+
|
|
|
+ if (_remainingDuration != null && _remainingDuration!.isNegative) {
|
|
|
+ _handleExpired();
|
|
|
+ } else {
|
|
|
+ _startTimer();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ _startNewCountdown();
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ print("加载倒计时时间出错: $e");
|
|
|
+ // 异常时调用修复后的_handleExpiredOver
|
|
|
+ _handleExpiredOver();
|
|
|
+ } finally {
|
|
|
+ if (mounted) {
|
|
|
+ setState(() {
|
|
|
+ _isLoading = false;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void _startNewCountdown() {
|
|
|
+ _startTime = DateTime.now();
|
|
|
+ _saveStartTime();
|
|
|
+ _updateRemainingTime();
|
|
|
+ _startTimer();
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<void> _saveStartTime() async {
|
|
|
+ try {
|
|
|
+ KVUtil.putInt(MemberDiscountCountdownTimeCount, _startTime!.millisecondsSinceEpoch);
|
|
|
+ } catch (e) {
|
|
|
+ print("保存倒计时时间出错: $e");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void _startTimer() {
|
|
|
+ // 2. 先取消可能存在的旧定时器(避免重复创建)
|
|
|
+ _timer?.cancel();
|
|
|
+ // 3. 重新创建定时器并赋值
|
|
|
+ _timer = Timer.periodic(Duration(seconds: 1), (timer) {
|
|
|
+ if (mounted) {
|
|
|
+ setState(() {
|
|
|
+ _updateRemainingTime();
|
|
|
+ if (_remainingDuration != null && _remainingDuration!.isNegative) {
|
|
|
+ _handleExpired();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ void _updateRemainingTime() {
|
|
|
+ if (_startTime == null) return;
|
|
|
+ final now = DateTime.now();
|
|
|
+ final endTime = _startTime!.add(widget.duration);
|
|
|
+ _remainingDuration = endTime.difference(now);
|
|
|
+ }
|
|
|
+
|
|
|
+ void _handleExpired() {
|
|
|
+ // 4. 安全取消定时器(允许_timer为null)
|
|
|
+ _timer?.cancel();
|
|
|
+ _isExpired = true;
|
|
|
+ if (mounted) {
|
|
|
+ widget.onExpired?.call();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ void _handleExpiredOver() {
|
|
|
+ // 5. 修复这里!使用?避免对null调用cancel()
|
|
|
+ _timer?.cancel();
|
|
|
+ _isExpired = true;
|
|
|
+ widget.onExpired?.call();
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void dispose() {
|
|
|
+ // 6. 销毁时安全取消
|
|
|
+ _timer?.cancel();
|
|
|
+ super.dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 以下代码不变...
|
|
|
+ String _formatDuration(Duration duration) {
|
|
|
+ final hours = duration.inHours;
|
|
|
+ final minutes = duration.inMinutes.remainder(60);
|
|
|
+ final seconds = duration.inSeconds.remainder(60);
|
|
|
+ return '${hours.toString().padLeft(2, '0')} : '
|
|
|
+ '${minutes.toString().padLeft(2, '0')} : '
|
|
|
+ '${seconds.toString().padLeft(2, '0')}';
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ if (_isLoading) {
|
|
|
+ return const CircularProgressIndicator();
|
|
|
+ }
|
|
|
+ if (_isExpired) {
|
|
|
+ return widget.expiredWidget ?? const SizedBox.shrink();
|
|
|
+ }
|
|
|
+ if (!_isVisible) {
|
|
|
+ return widget.placeholder ?? const SizedBox.shrink();
|
|
|
+ }
|
|
|
+ if (_remainingDuration == null || _remainingDuration!.isNegative) {
|
|
|
+ return _createCellWidget("00","00","00");
|
|
|
+ }
|
|
|
+ final hours = _remainingDuration!.inHours;
|
|
|
+ final minutes = _remainingDuration!.inMinutes.remainder(60);
|
|
|
+ final seconds = _remainingDuration!.inSeconds.remainder(60);
|
|
|
+ final hoursStr = hours.toString().padLeft(2, '0');
|
|
|
+ final minutesStr = minutes.toString().padLeft(2, '0');
|
|
|
+ final secondsStr = seconds.toString().padLeft(2, '0');
|
|
|
+ return _createCellWidget(hoursStr, minutesStr, secondsStr);
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _createCellWidget(String hoursStr, String minutesStr, String secondsStr) {
|
|
|
+ return Container(
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ Text(
|
|
|
+ "优惠限时",
|
|
|
+ style: TextStyle(
|
|
|
+ color: "#333333".color,
|
|
|
+ fontSize: 11.sp,
|
|
|
+ fontWeight: FontWeight.w700),
|
|
|
+ ),
|
|
|
+ SizedBox(width: 5.w),
|
|
|
+ _createItemWidget(hoursStr),
|
|
|
+ _createItemMidele(),
|
|
|
+ _createItemWidget(minutesStr),
|
|
|
+ _createItemMidele(),
|
|
|
+ _createItemWidget(secondsStr),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _createItemWidget(String timeStr) {
|
|
|
+ return Container(
|
|
|
+ padding: EdgeInsets.only(left: 2.w,right: 2.w),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ borderRadius: BorderRadius.all(Radius.circular(3)),
|
|
|
+ color: "#8B79F3".color
|
|
|
+ ),
|
|
|
+ child: Center(
|
|
|
+ child: Text(
|
|
|
+ timeStr,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 11.sp,
|
|
|
+ color: ColorName.white,
|
|
|
+ backgroundColor: "#8B79F3".color,
|
|
|
+ fontWeight: FontWeight.w500,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _createItemMidele() {
|
|
|
+ return Container(
|
|
|
+ alignment: Alignment.center,
|
|
|
+ width: 4.w,
|
|
|
+ child: Center(
|
|
|
+ child: Text(
|
|
|
+ ":",
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 11.sp,
|
|
|
+ color: "#8B79F3".color,
|
|
|
+ fontWeight: FontWeight.w400)),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|