import 'dart:async'; import 'package:flutter/material.dart'; import 'package:test_sa/controllers/api_routes/api_manager.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/string_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/timer_model.dart'; import 'package:test_sa/modules/cm_module/utilities/service_request_utils.dart'; import 'package:test_sa/views/widgets/date_and_time/date_picker.dart'; import '../../../new_views/app_style/app_color.dart'; class AppTimer extends StatefulWidget { final TimerModel? timer; final Future Function(TimerModel)? onChange; final TimerModel? pickerTimer; final DateTime? pickerFromDate; final Function(TimerModel?)? onPick; final TextStyle? style; final BoxDecoration? decoration; final bool enabled; final String? label; final double? width; final Function(bool)? timerProgress; const AppTimer({ Key? key, this.label, this.timer, this.onChange, this.style, this.decoration, this.width, this.pickerTimer, this.pickerFromDate, this.onPick, this.timerProgress, this.enabled = true, }) : super(key: key); @override State createState() => _AppTimerState(); } class _AppTimerState extends State { Timer? _timer; DateTime? _startAt; DateTime? _endAt; int _delay = 0; bool _running = false; bool _loading = false; bool canPickTime = false; final ValueNotifier _period = ValueNotifier("0:00:00"); DateTime? _pickerStartAt; DateTime? _pickerEndAt; TimerModel? _tempPickerTimer; void setPickerTime() async { int difference = _pickerStartAt == null ? 0 : (_pickerEndAt ?? DateTime.now()).difference(_pickerStartAt!).inSeconds; _tempPickerTimer = TimerModel(startAt: _pickerStartAt, endAt: _pickerEndAt, durationInSecond: difference); _pickerStartAt = null; _pickerEndAt = null; setState(() {}); widget.onPick!(_tempPickerTimer); } void deletePickerTime() { _tempPickerTimer = null; _pickerStartAt = null; _pickerEndAt = null; widget.onPick!(_tempPickerTimer); setState(() {}); } Future _startTimer() async { if (!_running && widget.onChange != null) { final time = DateTime.now(); bool result = await widget.onChange!(TimerModel(startAt: time, endAt: null, durationInSecond: _delay)); if (!result) return; _running = true; if (_endAt != null) { _delay += _endAt!.difference(_startAt!).inSeconds; } _startAt = time.subtract(Duration(seconds: _delay)); _endAt = null; } _timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (_loading) return; _period.value = (_endAt ?? DateTime.now()).difference(_startAt!).toString().split(".").first; }); } Future _stopTimer() async { if (widget.onChange != null) { final time = DateTime.now(); final tempStartAt = _startAt!.add(Duration(seconds: _delay)); bool result = await widget.onChange!(TimerModel(startAt: tempStartAt, endAt: time, durationInSecond: _delay)); if (!result) return; _running = false; _endAt = time; _startAt = tempStartAt; _timer?.cancel(); } } Future _onPressed() async { _loading = true; setState(() {}); if (!_running) { await _startTimer(); } else { await _stopTimer(); } _loading = false; setState(() {}); //widget.timerProgress?(_running); // Use null-aware operator to call timerProgress } @override void initState() { canPickTime = ApiManager.instance.assetGroup?.enabledEngineerTimer ?? false; _startAt = widget.timer?.startAt; _endAt = widget.timer?.endAt; _running = _startAt != null && _endAt == null; _delay = widget.timer?.durationInSecond ?? 0; int difference = _startAt == null ? 0 : (_endAt ?? DateTime.now()).difference(_startAt!).inSeconds; if (difference != 0 && _delay != 0) { difference = difference + _delay; } _period.value = Duration(seconds: _running ? difference : (difference != 0 ? difference : _delay)).toString().split(".").first; if (widget.pickerTimer != null) { _tempPickerTimer = widget.pickerTimer; } super.initState(); if (_running) { _startTimer(); } } @override void dispose() { _timer?.cancel(); // Cancel the timer when the widget is disposed super.dispose(); } @override Widget build(BuildContext context) { return Column( children: [ if (canPickTime) ...[ Row( mainAxisSize: MainAxisSize.min, children: [ ADatePicker( label: context.translation.startTime, hideShadow: true, backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90, date: _pickerStartAt, from: widget.pickerFromDate, enable: widget.enabled ? _tempPickerTimer == null : false, formatDateWithTime: true, onDatePicker: (selectedDate) { showTimePicker( context: context, initialTime: TimeOfDay.now(), builder: (BuildContext context, Widget? child) { final ThemeData currentTheme = Theme.of(context); return Theme( data: currentTheme.copyWith( timePickerTheme: TimePickerThemeData( dialHandColor: AppColor.primary10, dialBackgroundColor: Colors.grey.withOpacity(0.1), hourMinuteColor: MaterialStateColor.resolveWith((states) => states.contains(MaterialState.selected) ? AppColor.primary10 : Colors.grey.withOpacity(0.1)), dayPeriodColor: MaterialStateColor.resolveWith((states) => states.contains(MaterialState.selected) ? AppColor.primary10 : Colors.transparent), dayPeriodTextColor: MaterialStateColor.resolveWith((states) => states.contains(MaterialState.selected) ? Colors.white : AppColor.primary10), dayPeriodBorderSide: BorderSide(color: Colors.grey.withOpacity(0.2)), entryModeIconColor: AppColor.primary10, ), textButtonTheme: TextButtonThemeData(style: TextButton.styleFrom(foregroundColor: AppColor.primary10)), ), child: child!, ); }, ).then((selectedTime) { if (selectedTime != null) { _pickerStartAt = DateTime(selectedDate.year, selectedDate.month, selectedDate.day, selectedTime.hour, selectedTime.minute); if (widget.pickerFromDate != null && _pickerStartAt!.isBefore(widget.pickerFromDate!)) { "Start time is before the request time.".showToast; _pickerStartAt = null; selectedTime = null; return; } if (_pickerStartAt!.isAfter(DateTime.now())) { "Start time is after than current time".showToast; _pickerStartAt = null; selectedTime = null; return; } setState(() {}); } }); }, ).expanded, 8.width, ADatePicker( label: context.translation.endTime, hideShadow: true, backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90, enable: widget.enabled ? _pickerStartAt != null : false, from: _pickerStartAt, date: _pickerEndAt, to: DateTime.now(), formatDateWithTime: true, onDatePicker: (selectedDate) { showTimePicker( context: context, initialTime: TimeOfDay.now(), builder: (BuildContext context, Widget? child) { final ThemeData currentTheme = Theme.of(context); return Theme( data: currentTheme.copyWith( timePickerTheme: TimePickerThemeData( dialHandColor: AppColor.primary10, dialBackgroundColor: Colors.grey.withOpacity(0.1), hourMinuteColor: MaterialStateColor.resolveWith((states) => states.contains(MaterialState.selected) ? AppColor.primary10 : Colors.grey.withOpacity(0.1)), dayPeriodColor: MaterialStateColor.resolveWith((states) => states.contains(MaterialState.selected) ? AppColor.primary10 : Colors.transparent), dayPeriodTextColor: MaterialStateColor.resolveWith((states) => states.contains(MaterialState.selected) ? Colors.white : AppColor.primary10), dayPeriodBorderSide: BorderSide(color: Colors.grey.withOpacity(0.2)), entryModeIconColor: AppColor.primary10, ), textButtonTheme: TextButtonThemeData(style: TextButton.styleFrom(foregroundColor: AppColor.primary10)), ), child: child!, ); }, ).then((selectedTime) { if (selectedTime != null) { selectedDate = selectedDate.add(Duration(hours: selectedTime.hour, minutes: selectedTime.minute)); bool isBeforeCurrentTime = selectedDate.isBefore(DateTime.now()); bool isAfterStartTime = selectedDate.isAfter(_pickerStartAt!); if (!isBeforeCurrentTime) { "Please select a time before the current time.".showToast; return; } if (!isAfterStartTime) { "End Date time must be greater then start date".showToast; return; } _pickerEndAt = selectedDate; setPickerTime(); } }); }, ).expanded, ], ), if (_tempPickerTimer != null) ...[ 8.height, Row( children: [ Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ ServiceRequestUtils.formatTimerDurationCM(_tempPickerTimer!.durationInSecond!.round()).bodyText(context).custom(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), "Start at: ${_tempPickerTimer!.startAt!.toIso8601String().toFirstActionFormat}\nEnd at: ${_tempPickerTimer!.endAt!.toIso8601String().toFirstActionFormat}".tinyFont(context), ], ).expanded, 8.width, const Icon(Icons.delete_rounded, size: 20, color: Color(0xffF63939)).onPress(() { deletePickerTime(); }) ], ).toShadowContainer(context, padding: 8) ], 8.height, ], Container( // width: widget.width ?? 100 * AppStyle.getScaleFactor(context), height: 56.toScreenHeight, padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth), decoration: widget.decoration ?? BoxDecoration( color: context.isDark && !widget.enabled ? AppColor.neutral60 : !widget.enabled // backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90, ? AppColor.neutral40 : AppColor.fieldBgColor(context), borderRadius: BorderRadius.circular(10), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10)], ), child: _loading ? const SizedBox.square(dimension: 18, child: CircularProgressIndicator(color: Colors.white)) : Row( children: [ Expanded( child: ValueListenableBuilder( valueListenable: _period, builder: (context, value, _) { return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ (widget.label ?? "Timer").tinyFont(context), value.bodyText(context).custom( color: widget.enabled ? context.isDark ? AppColor.neutral30 : AppColor.neutral50 : AppColor.neutral20, ), ], ); }, ), ), if (widget.enabled) Icon(_running ? Icons.pause : Icons.play_arrow), ], ), ).onPress(_loading || !widget.enabled ? null : _onPressed), ], ); } } // class AppTimerPicker extends StatefulWidget { // final TimerModel? timer; // final Function(TimerModel?) onPick; // // AppTimerPicker({Key? key, this.timer, required this.onPick}) : super(key: key); // // @override // _AppTimerPickerState createState() { // return _AppTimerPickerState(); // } // } // // class _AppTimerPickerState extends State { // DateTime? _pickerStartAt; // DateTime? _pickerEndAt; // // TimerModel? _tempPickerTimer; // // @override // void initState() { // if (widget.timer != null) { // _tempPickerTimer = widget.timer; // } // super.initState(); // } // // void setTime() async { // int difference = _pickerStartAt == null ? 0 : (_pickerEndAt ?? DateTime.now()).difference(_pickerStartAt!).inSeconds; // _tempPickerTimer = TimerModel(startAt: _pickerStartAt, endAt: _pickerEndAt, durationInSecond: difference); // // timerList.add(_tempModel); // // await widget.onChange!(_tempModel); // _pickerStartAt = null; // _pickerEndAt = null; // setState(() {}); // widget.onPick(_tempPickerTimer); // } // // void deleteTime() { // _tempPickerTimer = null; // _pickerStartAt = null; // _pickerEndAt = null; // widget.onPick(_tempPickerTimer); // setState(() {}); // } // // @override // void dispose() { // super.dispose(); // } // // @override // Widget build(BuildContext context) { // return Column( // children: [ // Row( // mainAxisSize: MainAxisSize.min, // children: [ // ADatePicker( // label: context.translation.startTime, // hideShadow: true, // backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90, // date: _pickerStartAt, // enable: _tempPickerTimer == null, // formatDateWithTime: true, // onDatePicker: (selectedDate) { // showTimePicker( // context: context, // initialTime: TimeOfDay.now(), // ).then((selectedTime) { // if (selectedTime != null) { // _pickerStartAt = DateTime(selectedDate.year, selectedDate.month, selectedDate.day, selectedTime.hour, selectedTime.minute); // setState(() {}); // } // }); // }, // ).expanded, // 8.width, // ADatePicker( // label: context.translation.endTime, // hideShadow: true, // backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90, // enable: _pickerStartAt != null, // date: _pickerEndAt, // formatDateWithTime: true, // onDatePicker: (selectedDate) { // showTimePicker( // context: context, // initialTime: TimeOfDay.now(), // ).then((selectedTime) { // if (selectedTime != null) { // DateTime selectedDateTime = DateTime(selectedDate.year, selectedDate.month, selectedDate.day, selectedTime.hour, selectedTime.minute); // if (_pickerStartAt != null && selectedDateTime.isBefore(_pickerStartAt!)) { // "End Date time must be greater then start date".showToast; // return; // } // _pickerEndAt = selectedDateTime; // setTime(); // } // }); // }, // ).expanded, // ], // ), // if (_tempPickerTimer != null) ...[ // 8.height, // Row( // children: [ // Column( // mainAxisSize: MainAxisSize.min, // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // ServiceRequestUtils.formatTimerDurationCM(_tempPickerTimer!.durationInSecond!.round()).bodyText(context).custom(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), // "Start at: ${_tempPickerTimer!.startAt!.toIso8601String().toFirstActionFormat}\nEnd at: ${_tempPickerTimer!.endAt!.toIso8601String().toFirstActionFormat}".tinyFont(context), // ], // ).expanded, // 8.width, // const Icon(Icons.delete_rounded, size: 20, color: Color(0xffF63939)).onPress(() { // deleteTime(); // }) // ], // ).toShadowContainer(context, padding: 8) // ] // ], // ); // } // }