Merge pull request #138 from SyncrowIOT/SP-1192-FE-Implement-the-schedule-on-the-web-for-the-AC-device

Sp 1192 fe implement the schedule on the web for the ac device
This commit is contained in:
mohammadnemer1
2025-04-13 15:02:02 +03:00
committed by GitHub
7 changed files with 384 additions and 100 deletions

View File

@ -201,20 +201,17 @@ class ForgetPasswordWebPage extends StatelessWidget {
!state.isButtonEnabled && !state.isButtonEnabled &&
state.remainingTime != 1 state.remainingTime != 1
? null ? null
: () { :
() {
if (forgetBloc if (forgetBloc
.forgetEmailKey.currentState! .forgetEmailKey
.validate() || .currentState!
forgetBloc .validate()) {
.forgetRegionKey.currentState! forgetBloc.add(
.validate()) { StartTimerEvent());
if (forgetBloc
.forgetRegionKey.currentState!
.validate()) {
forgetBloc.add(StartTimerEvent());
}
} }
}, },
child: Text( child: Text(
'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}', 'Get Code ${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "(${forgetBloc.formattedTime(state.remainingTime)}) " : ""}',
style: TextStyle( style: TextStyle(

View File

@ -13,6 +13,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
late AcStatusModel deviceStatus; late AcStatusModel deviceStatus;
final String deviceId; final String deviceId;
Timer? _timer; Timer? _timer;
Timer? _countdownTimer;
AcBloc({required this.deviceId}) : super(AcsInitialState()) { AcBloc({required this.deviceId}) : super(AcsInitialState()) {
on<AcFetchDeviceStatusEvent>(_onFetchAcStatus); on<AcFetchDeviceStatusEvent>(_onFetchAcStatus);
@ -21,7 +22,16 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
on<AcBatchControlEvent>(_onAcBatchControl); on<AcBatchControlEvent>(_onAcBatchControl);
on<AcFactoryResetEvent>(_onFactoryReset); on<AcFactoryResetEvent>(_onFactoryReset);
on<AcStatusUpdated>(_onAcStatusUpdated); on<AcStatusUpdated>(_onAcStatusUpdated);
on<OnClose>(_onClose);
on<IncreaseTimeEvent>(_handleIncreaseTime);
on<DecreaseTimeEvent>(_handleDecreaseTime);
on<UpdateTimerEvent>(_handleUpdateTimer);
on<ToggleScheduleEvent>(_handleToggleTimer);
on<ApiCountdownValueEvent>(_handleApiCountdownValue);
} }
bool timerActive = false;
int scheduledHours = 0;
int scheduledMinutes = 0;
FutureOr<void> _onFetchAcStatus( FutureOr<void> _onFetchAcStatus(
AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async { AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async {
@ -30,8 +40,23 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
final status = final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId); await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
if (deviceStatus.countdown1 != 0) {
// Convert API value to minutes
final totalMinutes = deviceStatus.countdown1 * 6;
scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60;
timerActive = true;
_startCountdownTimer(emit);
}
emit(ACStatusLoaded(
status: deviceStatus,
scheduledHours: scheduledHours,
scheduledMinutes: scheduledMinutes,
isTimerActive: timerActive,
));
_listenToChanges(event.deviceId); _listenToChanges(event.deviceId);
emit(ACStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(AcsFailedState(error: e.toString())); emit(AcsFailedState(error: e.toString()));
} }
@ -70,31 +95,16 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
void _onAcStatusUpdated(AcStatusUpdated event, Emitter<AcsState> emit) { void _onAcStatusUpdated(AcStatusUpdated event, Emitter<AcsState> emit) {
deviceStatus = event.deviceStatus; deviceStatus = event.deviceStatus;
emit(ACStatusLoaded(deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} }
// Future<void> testFirebaseConnection() async {
// // Reference to a test node in your database
// final testRef = FirebaseDatabase.instance.ref("test");
// // Write a test value
// await testRef.set("Hello, Firebase!");
// // Listen for changes on the test node
// testRef.onValue.listen((DatabaseEvent event) {
// final data = event.snapshot.value;
// print("Data from Firebase: $data");
// // If you see "Hello, Firebase!" printed in your console, it means the connection works.
// });
// }
FutureOr<void> _onAcControl( FutureOr<void> _onAcControl(
AcControlEvent event, Emitter<AcsState> emit) async { AcControlEvent event, Emitter<AcsState> emit) async {
final oldValue = _getValueByCode(event.code); final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value, emit); _updateLocalValue(event.code, event.value, emit);
emit(ACStatusLoaded(deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce( await _runDebounce(
isBatch: false, isBatch: false,
@ -151,7 +161,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
void _revertValueAndEmit( void _revertValueAndEmit(
String deviceId, String code, dynamic oldValue, Emitter<AcsState> emit) { String deviceId, String code, dynamic oldValue, Emitter<AcsState> emit) {
_updateLocalValue(code, oldValue, emit); _updateLocalValue(code, oldValue, emit);
emit(ACStatusLoaded(deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} }
void _updateLocalValue(String code, dynamic value, Emitter<AcsState> emit) { void _updateLocalValue(String code, dynamic value, Emitter<AcsState> emit) {
@ -184,11 +194,16 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
if (value is bool) { if (value is bool) {
deviceStatus = deviceStatus.copyWith(childLock: value); deviceStatus = deviceStatus.copyWith(childLock: value);
} }
case 'countdown_time':
if (value is int) {
deviceStatus = deviceStatus.copyWith(countdown1: value);
}
break; break;
default: default:
break; break;
} }
emit(ACStatusLoaded(deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} }
dynamic _getValueByCode(String code) { dynamic _getValueByCode(String code) {
@ -203,6 +218,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
return deviceStatus.fanSpeedsString; return deviceStatus.fanSpeedsString;
case 'child_lock': case 'child_lock':
return deviceStatus.childLock; return deviceStatus.childLock;
case 'countdown_time':
return deviceStatus.countdown1;
default: default:
return null; return null;
} }
@ -216,7 +233,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
await DevicesManagementApi().getBatchStatus(event.devicesIds); await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = deviceStatus =
AcStatusModel.fromJson(event.devicesIds.first, status.status); AcStatusModel.fromJson(event.devicesIds.first, status.status);
emit(ACStatusLoaded(deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} catch (e) { } catch (e) {
emit(AcsFailedState(error: e.toString())); emit(AcsFailedState(error: e.toString()));
} }
@ -228,7 +245,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
_updateLocalValue(event.code, event.value, emit); _updateLocalValue(event.code, event.value, emit);
emit(ACStatusLoaded(deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce( await _runDebounce(
isBatch: true, isBatch: true,
@ -257,4 +274,136 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
emit(AcsFailedState(error: e.toString())); emit(AcsFailedState(error: e.toString()));
} }
} }
void _onClose(OnClose event, Emitter<AcsState> emit) {
_countdownTimer?.cancel();
_timer?.cancel();
}
void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) {
if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded;
int newHours = scheduledHours;
int newMinutes = scheduledMinutes + 30;
newHours += newMinutes ~/ 60;
newMinutes = newMinutes % 60;
if (newHours > 23) {
newHours = 23;
newMinutes = 59;
}
scheduledHours = newHours;
scheduledMinutes = newMinutes;
emit(currentState.copyWith(
scheduledHours: scheduledHours,
scheduledMinutes: scheduledMinutes,
));
}
void _handleDecreaseTime(DecreaseTimeEvent event, Emitter<AcsState> emit) {
if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded;
int totalMinutes = (scheduledHours * 60) + scheduledMinutes;
totalMinutes = (totalMinutes - 30).clamp(0, 1440);
scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60;
emit(currentState.copyWith(
scheduledHours: scheduledHours,
scheduledMinutes: scheduledMinutes,
));
}
Future<void> _handleToggleTimer(
ToggleScheduleEvent event, Emitter<AcsState> emit) async {
if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded;
timerActive = !timerActive;
if (timerActive) {
final totalMinutes = scheduledHours * 60 + scheduledMinutes;
if (totalMinutes <= 0) {
timerActive = false;
emit(currentState.copyWith(isTimerActive: timerActive));
return;
}
try {
final scaledValue = totalMinutes ~/ 6;
await DevicesManagementApi().deviceControl(
deviceId,
Status(code: 'countdown_time', value: scaledValue),
);
_startCountdownTimer(emit);
emit(currentState.copyWith(isTimerActive: timerActive));
} catch (e) {
timerActive = false;
emit(AcsFailedState(error: e.toString()));
}
} else {
await DevicesManagementApi().deviceControl(
deviceId,
Status(code: 'countdown_time', value: 0),
);
_countdownTimer?.cancel();
scheduledHours = 0;
scheduledMinutes = 0;
emit(currentState.copyWith(
isTimerActive: timerActive,
scheduledHours: 0,
scheduledMinutes: 0,
));
}
}
void _startCountdownTimer(Emitter<AcsState> emit) {
_countdownTimer?.cancel();
int totalSeconds = (scheduledHours * 3600) + (scheduledMinutes * 60);
_countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (totalSeconds > 0) {
totalSeconds--;
scheduledHours = totalSeconds ~/ 3600;
scheduledMinutes = (totalSeconds % 3600) ~/ 60;
add(UpdateTimerEvent());
} else {
_countdownTimer?.cancel();
timerActive = false;
scheduledHours = 0;
scheduledMinutes = 0;
add(TimerCompletedEvent());
}
});
}
void _handleUpdateTimer(UpdateTimerEvent event, Emitter<AcsState> emit) {
if (state is ACStatusLoaded) {
final currentState = state as ACStatusLoaded;
emit(currentState.copyWith(
scheduledHours: scheduledHours,
scheduledMinutes: scheduledMinutes,
isTimerActive: timerActive,
));
}
}
void _handleApiCountdownValue(
ApiCountdownValueEvent event, Emitter<AcsState> emit) {
if (state is ACStatusLoaded) {
final totalMinutes = event.apiValue * 6;
final scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60;
_startCountdownTimer(
emit,
);
add(UpdateTimerEvent());
}
}
@override
Future<void> close() {
add(OnClose());
return super.close();
}
} }

View File

@ -8,6 +8,7 @@ sealed class AcsEvent extends Equatable {
@override @override
List<Object> get props => []; List<Object> get props => [];
} }
class AcUpdated extends AcsEvent {} class AcUpdated extends AcsEvent {}
class AcFetchDeviceStatusEvent extends AcsEvent { class AcFetchDeviceStatusEvent extends AcsEvent {
@ -18,10 +19,12 @@ class AcFetchDeviceStatusEvent extends AcsEvent {
@override @override
List<Object> get props => [deviceId]; List<Object> get props => [deviceId];
} }
class AcStatusUpdated extends AcsEvent { class AcStatusUpdated extends AcsEvent {
final AcStatusModel deviceStatus; final AcStatusModel deviceStatus;
AcStatusUpdated(this.deviceStatus); AcStatusUpdated(this.deviceStatus);
} }
class AcFetchBatchStatusEvent extends AcsEvent { class AcFetchBatchStatusEvent extends AcsEvent {
final List<String> devicesIds; final List<String> devicesIds;
@ -73,3 +76,30 @@ class AcFactoryResetEvent extends AcsEvent {
@override @override
List<Object> get props => [deviceId, factoryResetModel]; List<Object> get props => [deviceId, factoryResetModel];
} }
class OnClose extends AcsEvent {}
class IncreaseTimeEvent extends AcsEvent {
@override
List<Object> get props => [];
}
class DecreaseTimeEvent extends AcsEvent {
@override
List<Object> get props => [];
}
class ToggleScheduleEvent extends AcsEvent {}
class TimerCompletedEvent extends AcsEvent {}
class UpdateTimerEvent extends AcsEvent {
}
class ApiCountdownValueEvent extends AcsEvent {
final int apiValue;
const ApiCountdownValueEvent(this.apiValue);
}

View File

@ -2,8 +2,9 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
abstract class AcsState extends Equatable { abstract class AcsState extends Equatable {
const AcsState(); final bool isTimerActive;
const AcsState({this.isTimerActive = false});
@override @override
List<Object> get props => []; List<Object> get props => [];
} }
@ -15,8 +16,30 @@ class AcsLoadingState extends AcsState {}
class ACStatusLoaded extends AcsState { class ACStatusLoaded extends AcsState {
final AcStatusModel status; final AcStatusModel status;
final DateTime timestamp; final DateTime timestamp;
final int scheduledHours;
final int scheduledMinutes;
final bool isTimerActive;
ACStatusLoaded(this.status) : timestamp = DateTime.now(); ACStatusLoaded({
required this.status,
this.scheduledHours = 0,
this.scheduledMinutes = 0,
this.isTimerActive = false,
}) : timestamp = DateTime.now();
ACStatusLoaded copyWith({
AcStatusModel? status,
int? scheduledHours,
int? scheduledMinutes,
bool? isTimerActive,
int? remainingTime,
}) {
return ACStatusLoaded(
status: status ?? this.status,
scheduledHours: scheduledHours ?? this.scheduledHours,
scheduledMinutes: scheduledMinutes ?? this.scheduledMinutes,
isTimerActive: isTimerActive ?? this.isTimerActive,
);
}
@override @override
List<Object> get props => [status, timestamp]; List<Object> get props => [status, timestamp];
@ -40,3 +63,14 @@ class AcsFailedState extends AcsState {
@override @override
List<Object> get props => [error]; List<Object> get props => [error];
} }
class TimerRunInProgress extends AcsState {
final int remainingTime;
const TimerRunInProgress(this.remainingTime);
@override
List<Object> get props => [remainingTime];
}

View File

@ -11,6 +11,7 @@ class AcStatusModel {
final bool childLock; final bool childLock;
final TempModes acMode; final TempModes acMode;
final FanSpeeds acFanSpeed; final FanSpeeds acFanSpeed;
final int countdown1;
AcStatusModel({ AcStatusModel({
required this.uuid, required this.uuid,
@ -18,6 +19,7 @@ class AcStatusModel {
required this.modeString, required this.modeString,
required this.tempSet, required this.tempSet,
required this.currentTemp, required this.currentTemp,
required this.countdown1,
required this.fanSpeedsString, required this.fanSpeedsString,
required this.childLock, required this.childLock,
}) : acMode = getACMode(modeString), }) : acMode = getACMode(modeString),
@ -30,6 +32,7 @@ class AcStatusModel {
late int currentTemp; late int currentTemp;
late String fanSpeeds; late String fanSpeeds;
late bool childLock; late bool childLock;
late int _countdown1 = 0;
for (var status in jsonList) { for (var status in jsonList) {
switch (status.code) { switch (status.code) {
@ -51,6 +54,9 @@ class AcStatusModel {
case 'child_lock': case 'child_lock':
childLock = status.value ?? false; childLock = status.value ?? false;
break; break;
case 'countdown_time':
_countdown1 = status.value ?? 0;
break;
} }
} }
@ -62,6 +68,7 @@ class AcStatusModel {
currentTemp: currentTemp, currentTemp: currentTemp,
fanSpeedsString: fanSpeeds, fanSpeedsString: fanSpeeds,
childLock: childLock, childLock: childLock,
countdown1: _countdown1,
); );
} }
@ -73,6 +80,7 @@ class AcStatusModel {
int? currentTemp, int? currentTemp,
String? fanSpeedsString, String? fanSpeedsString,
bool? childLock, bool? childLock,
int? countdown1,
}) { }) {
return AcStatusModel( return AcStatusModel(
uuid: uuid ?? this.uuid, uuid: uuid ?? this.uuid,
@ -82,6 +90,7 @@ class AcStatusModel {
currentTemp: currentTemp ?? this.currentTemp, currentTemp: currentTemp ?? this.currentTemp,
fanSpeedsString: fanSpeedsString ?? this.fanSpeedsString, fanSpeedsString: fanSpeedsString ?? this.fanSpeedsString,
childLock: childLock ?? this.childLock, childLock: childLock ?? this.childLock,
countdown1: countdown1 ?? this.countdown1,
); );
} }

View File

@ -10,11 +10,10 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
const AcDeviceControlsView({super.key, required this.device}); const AcDeviceControlsView({super.key, required this.device});
final AllDevicesModel device; final AllDevicesModel device;
@ -23,11 +22,13 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
final isExtraLarge = isExtraLargeScreenSize(context); final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
create: (context) => AcBloc(deviceId: device.uuid!) create: (context) => AcBloc(deviceId: device.uuid!)
..add(AcFetchDeviceStatusEvent(device.uuid!)), ..add(AcFetchDeviceStatusEvent(device.uuid!)),
child: BlocBuilder<AcBloc, AcsState>( child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) { builder: (context, state) {
final acBloc = BlocProvider.of<AcBloc>(context);
if (state is ACStatusLoaded) { if (state is ACStatusLoaded) {
return GridView( return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50), padding: const EdgeInsets.symmetric(horizontal: 50),
@ -78,56 +79,101 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
), ),
ToggleWidget( ToggleWidget(
label: '', label: '',
labelWidget: Row( labelWidget: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
IconButton( Container(
padding: const EdgeInsets.all(0), width: MediaQuery.of(context).size.width,
onPressed: () {}, decoration: const ShapeDecoration(
icon: const Icon( color: ColorsManager.primaryColor,
Icons.remove, shape: RoundedRectangleBorder(
size: 28, borderRadius: BorderRadius.all(Radius.circular(30)),
color: ColorsManager.greyColor, ),
), ),
), ),
Text( Center(
'06', child: SizedBox(
style: context.textTheme.titleLarge!.copyWith( child: Row(
color: ColorsManager.dialogBlueTitle, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
fontWeight: FontWeight.bold, children: [
), IconButton(
), onPressed: () {
Text( if (acBloc.timerActive == false) {
'h', context
style: context.textTheme.bodySmall! .read<AcBloc>()
.copyWith(color: ColorsManager.blackColor), .add(DecreaseTimeEvent());
), }
Text( },
'30', icon: const Icon(Icons.remove,
style: context.textTheme.titleLarge!.copyWith( color: ColorsManager.greyColor),
color: ColorsManager.dialogBlueTitle, ),
fontWeight: FontWeight.bold, Text(
), acBloc.scheduledHours
), .toString()
Text('m', .padLeft(2, '0'),
style: context.textTheme.bodySmall! style: Theme.of(context)
.copyWith(color: ColorsManager.blackColor)), .textTheme
IconButton( .titleLarge!
padding: const EdgeInsets.all(0), .copyWith(
onPressed: () {}, color: ColorsManager.dialogBlueTitle,
icon: const Icon( fontWeight: FontWeight.bold,
Icons.add, ),
size: 28, ),
color: ColorsManager.greyColor, Text(
'h',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.blackColor,
),
),
Text(
acBloc.scheduledMinutes
.toString()
.padLeft(2, '0'),
style: Theme.of(context)
.textTheme
.titleLarge!
.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
Text(
'm',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: ColorsManager.blackColor,
),
),
IconButton(
onPressed: () {
if (acBloc.timerActive == false) {
context
.read<AcBloc>()
.add(IncreaseTimeEvent());
}
},
icon: const Icon(Icons.add,
color: ColorsManager.greyColor),
),
],
),
), ),
), ),
], ],
), ),
value: false, value: acBloc.timerActive,
code: 'ac_schedule', code: 'ac_schedule',
deviceId: device.uuid!, deviceId: device.uuid!,
icon: Assets.acSchedule, icon: Assets.acSchedule,
onChange: (value) {}, onChange: (value) {
context.read<AcBloc>().add(ToggleScheduleEvent());
},
), ),
ToggleWidget( ToggleWidget(
deviceId: device.uuid!, deviceId: device.uuid!,

View File

@ -1,4 +1,3 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -28,9 +27,12 @@ class _TimeWheelPickerState extends State<TimeWheelPicker> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_hoursController = FixedExtentScrollController(initialItem: widget.initialHours); _hoursController =
_minutesController = FixedExtentScrollController(initialItem: widget.initialMinutes); FixedExtentScrollController(initialItem: widget.initialHours);
_secondsController = FixedExtentScrollController(initialItem: widget.initialSeconds); _minutesController =
FixedExtentScrollController(initialItem: widget.initialMinutes);
_secondsController =
FixedExtentScrollController(initialItem: widget.initialSeconds);
} }
@override @override
@ -47,6 +49,8 @@ class _TimeWheelPickerState extends State<TimeWheelPicker> {
} }
} }
@override @override
void dispose() { void dispose() {
_hoursController.dispose(); _hoursController.dispose();
@ -61,26 +65,28 @@ class _TimeWheelPickerState extends State<TimeWheelPicker> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
_buildPickerColumn( _buildPickerColumn(
label: 'h', label: 'h',
controller: _hoursController, controller: _hoursController,
itemCount: 24, itemCount: 3,
onChanged: (value) => _handleTimeChange( onChanged: (value) {
value, _handleTimeChange(
_minutesController.selectedItem, value,
_secondsController.selectedItem, _minutesController.selectedItem,
), _secondsController.selectedItem,
), );
}),
const SizedBox(width: 5), const SizedBox(width: 5),
_buildPickerColumn( _buildPickerColumn(
label: 'm', label: 'm',
controller: _minutesController, controller: _minutesController,
itemCount: 60, itemCount: 60,
onChanged: (value) => _handleTimeChange( onChanged: (value) {
_hoursController.selectedItem, _handleTimeChange(
value, _hoursController.selectedItem,
_secondsController.selectedItem, value,
), _secondsController.selectedItem,
), );
}),
const SizedBox(width: 5), const SizedBox(width: 5),
_buildPickerColumn( _buildPickerColumn(
label: 's', label: 's',
@ -97,6 +103,19 @@ class _TimeWheelPickerState extends State<TimeWheelPicker> {
} }
void _handleTimeChange(int hours, int minutes, int seconds) { void _handleTimeChange(int hours, int minutes, int seconds) {
int total = hours * 3600 + minutes * 60 + seconds;
if (total > 10000) {
hours = 2;
minutes = 46;
seconds = 40;
total = 10000;
WidgetsBinding.instance.addPostFrameCallback((_) {
_hoursController.jumpToItem(hours);
_minutesController.jumpToItem(minutes);
_secondsController.jumpToItem(seconds);
});
}
widget.onTimeChanged(hours, minutes, seconds); widget.onTimeChanged(hours, minutes, seconds);
} }
@ -147,4 +166,4 @@ class _TimeWheelPickerState extends State<TimeWheelPicker> {
], ],
); );
} }
} }