You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
461 lines
18 KiB
Dart
461 lines
18 KiB
Dart
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<bool> 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<AppTimer> createState() => _AppTimerState();
|
|
}
|
|
|
|
class _AppTimerState extends State<AppTimer> {
|
|
Timer? _timer;
|
|
DateTime? _startAt;
|
|
DateTime? _endAt;
|
|
int _delay = 0;
|
|
bool _running = false;
|
|
bool _loading = false;
|
|
bool canPickTime = false;
|
|
final ValueNotifier<String> _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<void> _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<void> _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<void> _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<String>(
|
|
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<AppTimerPicker> {
|
|
// 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)
|
|
// ]
|
|
// ],
|
|
// );
|
|
// }
|
|
// }
|