member_discount_countdown_widget.dart 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_screenutil/flutter_screenutil.dart';
  3. import 'package:location/resource/colors.gen.dart';
  4. import 'package:location/utils/common_expand.dart';
  5. import 'dart:async';
  6. import '../../utils/mmkv_util.dart';
  7. final String MemberDiscountCountdownTimeCount = 'key_member_discount_countdown_time_count';
  8. class MemberDiscountCountdownWidget extends StatefulWidget {
  9. final Duration duration; // 倒计时总时长(默认1小时)
  10. final TextStyle? textStyle;
  11. final Duration animationDuration;
  12. final Widget? placeholder;
  13. final Widget? expiredWidget; // 已过期时显示的组件
  14. final VoidCallback? onExpired; // 倒计时结束回调
  15. const MemberDiscountCountdownWidget({
  16. Key? key,
  17. this.duration = const Duration(hours: 1),
  18. this.textStyle,
  19. this.animationDuration = const Duration(milliseconds: 500),
  20. this.placeholder,
  21. this.expiredWidget,
  22. this.onExpired,
  23. }) : super(key: key);
  24. @override
  25. _MemberDiscountCountdownWidgetState createState() => _MemberDiscountCountdownWidgetState();
  26. }
  27. class _MemberDiscountCountdownWidgetState extends State<MemberDiscountCountdownWidget> with SingleTickerProviderStateMixin {
  28. // 1. 将_timer改为可空类型,移除late
  29. Timer? _timer;
  30. bool _isVisible = true;
  31. bool _isExpired = false;
  32. DateTime? _startTime;
  33. Duration? _remainingDuration;
  34. bool _isLoading = true;
  35. @override
  36. void initState() {
  37. super.initState();
  38. _loadStartTime();
  39. }
  40. Future<void> _loadStartTime() async {
  41. try {
  42. final savedTime = KVUtil.getInt(MemberDiscountCountdownTimeCount, 0);
  43. print("savetimefdifjd---${savedTime}");
  44. if (savedTime != null && savedTime > 0) {
  45. _startTime = DateTime.fromMillisecondsSinceEpoch(savedTime);
  46. _updateRemainingTime();
  47. if (_remainingDuration != null && _remainingDuration!.isNegative) {
  48. _handleExpired();
  49. } else {
  50. _startTimer();
  51. }
  52. } else {
  53. _startNewCountdown();
  54. }
  55. } catch (e) {
  56. print("加载倒计时时间出错: $e");
  57. // 异常时调用修复后的_handleExpiredOver
  58. _handleExpiredOver();
  59. } finally {
  60. if (mounted) {
  61. setState(() {
  62. _isLoading = false;
  63. });
  64. }
  65. }
  66. }
  67. void _startNewCountdown() {
  68. _startTime = DateTime.now();
  69. _saveStartTime();
  70. _updateRemainingTime();
  71. _startTimer();
  72. }
  73. Future<void> _saveStartTime() async {
  74. try {
  75. KVUtil.putInt(MemberDiscountCountdownTimeCount, _startTime!.millisecondsSinceEpoch);
  76. } catch (e) {
  77. print("保存倒计时时间出错: $e");
  78. }
  79. }
  80. void _startTimer() {
  81. // 2. 先取消可能存在的旧定时器(避免重复创建)
  82. _timer?.cancel();
  83. // 3. 重新创建定时器并赋值
  84. _timer = Timer.periodic(Duration(seconds: 1), (timer) {
  85. if (mounted) {
  86. setState(() {
  87. _updateRemainingTime();
  88. if (_remainingDuration != null && _remainingDuration!.isNegative) {
  89. _handleExpired();
  90. }
  91. });
  92. }
  93. });
  94. }
  95. void _updateRemainingTime() {
  96. if (_startTime == null) return;
  97. final now = DateTime.now();
  98. final endTime = _startTime!.add(widget.duration);
  99. _remainingDuration = endTime.difference(now);
  100. }
  101. void _handleExpired() {
  102. // 4. 安全取消定时器(允许_timer为null)
  103. _timer?.cancel();
  104. _isExpired = true;
  105. if (mounted) {
  106. widget.onExpired?.call();
  107. }
  108. }
  109. void _handleExpiredOver() {
  110. // 5. 修复这里!使用?避免对null调用cancel()
  111. _timer?.cancel();
  112. _isExpired = true;
  113. widget.onExpired?.call();
  114. }
  115. @override
  116. void dispose() {
  117. // 6. 销毁时安全取消
  118. _timer?.cancel();
  119. super.dispose();
  120. }
  121. // 以下代码不变...
  122. String _formatDuration(Duration duration) {
  123. final hours = duration.inHours;
  124. final minutes = duration.inMinutes.remainder(60);
  125. final seconds = duration.inSeconds.remainder(60);
  126. return '${hours.toString().padLeft(2, '0')} : '
  127. '${minutes.toString().padLeft(2, '0')} : '
  128. '${seconds.toString().padLeft(2, '0')}';
  129. }
  130. @override
  131. Widget build(BuildContext context) {
  132. if (_isLoading) {
  133. return const CircularProgressIndicator();
  134. }
  135. if (_isExpired) {
  136. return widget.expiredWidget ?? const SizedBox.shrink();
  137. }
  138. if (!_isVisible) {
  139. return widget.placeholder ?? const SizedBox.shrink();
  140. }
  141. if (_remainingDuration == null || _remainingDuration!.isNegative) {
  142. return _createCellWidget("00","00","00");
  143. }
  144. final hours = _remainingDuration!.inHours;
  145. final minutes = _remainingDuration!.inMinutes.remainder(60);
  146. final seconds = _remainingDuration!.inSeconds.remainder(60);
  147. final hoursStr = hours.toString().padLeft(2, '0');
  148. final minutesStr = minutes.toString().padLeft(2, '0');
  149. final secondsStr = seconds.toString().padLeft(2, '0');
  150. return _createCellWidget(hoursStr, minutesStr, secondsStr);
  151. }
  152. Widget _createCellWidget(String hoursStr, String minutesStr, String secondsStr) {
  153. return Container(
  154. child: Row(
  155. children: [
  156. Text(
  157. "优惠限时",
  158. style: TextStyle(
  159. color: "#333333".color,
  160. fontSize: 11.sp,
  161. fontWeight: FontWeight.w700),
  162. ),
  163. SizedBox(width: 5.w),
  164. _createItemWidget(hoursStr),
  165. _createItemMidele(),
  166. _createItemWidget(minutesStr),
  167. _createItemMidele(),
  168. _createItemWidget(secondsStr),
  169. ],
  170. ),
  171. );
  172. }
  173. Widget _createItemWidget(String timeStr) {
  174. return Container(
  175. height: 15.w, // 确保容器有足够高度
  176. alignment: Alignment.center,
  177. decoration: BoxDecoration(
  178. borderRadius: BorderRadius.circular(3),
  179. color: "#8B79F3".color,
  180. ),
  181. padding: EdgeInsets.symmetric(horizontal: 2.w),
  182. child: Text(
  183. timeStr,
  184. style: TextStyle(
  185. fontSize: 11.sp,
  186. color: ColorName.white,
  187. fontWeight: FontWeight.w500,
  188. ),
  189. ),
  190. );
  191. }
  192. Widget _createItemMidele() {
  193. return Container(
  194. alignment: Alignment.center,
  195. width: 4.w,
  196. child: Center(
  197. child: Text(
  198. ":",
  199. style: TextStyle(
  200. fontSize: 11.sp,
  201. color: "#8B79F3".color,
  202. fontWeight: FontWeight.w400)),
  203. ),
  204. );
  205. }
  206. }