Files
syncrow-web/lib/pages/device_managment/water_heater/widgets/schedual_view.dart
2024-09-22 14:17:52 +03:00

738 lines
25 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class BuildScheduleView extends StatefulWidget {
const BuildScheduleView({super.key, required this.status});
final WaterHeaterStatusModel status;
@override
_BuildScheduleViewState createState() => _BuildScheduleViewState();
}
class _BuildScheduleViewState extends State<BuildScheduleView> {
@override
Widget build(BuildContext context) {
final bloc = BlocProvider.of<WaterHeaterBloc>(context);
return BlocProvider.value(
value: bloc,
child: Dialog(
backgroundColor: Colors.white,
insetPadding: const EdgeInsets.all(20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: SizedBox(
width: 700,
child: SingleChildScrollView(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 40.0, vertical: 20),
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
builder: (context, state) {
if (state is WaterHeaterDeviceStatusLoaded) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_scheduleHeader(context),
const SizedBox(height: 20),
_buildScheduleModeSelector(context, state),
const SizedBox(height: 20),
if (state.scheduleMode == ScheduleModes.schedule)
_buildScheduleManagementUI(state),
if (state.scheduleMode == ScheduleModes.countdown ||
state.scheduleMode == ScheduleModes.inching)
..._buildCountDownAngInchingView(context, state),
const SizedBox(height: 20),
_buildSaveStopCancelButtons(context, state),
],
);
}
return const SizedBox();
},
),
),
),
),
),
);
}
Row _scheduleHeader(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(),
Text(
'Scheduling',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 22,
color: ColorsManager.dialogBlueTitle,
),
),
Container(
width: 25,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle,
border: Border.all(
color: Colors.grey,
width: 1.0,
),
),
child: IconButton(
padding: EdgeInsets.all(1),
icon: const Icon(
Icons.close,
color: Colors.grey,
size: 18,
),
onPressed: () {
Navigator.of(context).pop();
},
),
),
],
);
}
Widget _buildScheduleModeSelector(
BuildContext context, WaterHeaterDeviceStatusLoaded state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Type:',
style: context.textTheme.bodySmall!.copyWith(
fontSize: 13,
color: ColorsManager.grayColor,
),
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildRadioTile(
context, 'Countdown', ScheduleModes.countdown, state),
_buildRadioTile(context, 'Schedule', ScheduleModes.schedule, state),
_buildRadioTile(
context, 'Circulate', ScheduleModes.circulate, state),
_buildRadioTile(context, 'Inching', ScheduleModes.inching, state),
],
),
],
);
}
Widget _buildRadioTile(BuildContext context, String label, ScheduleModes mode,
WaterHeaterDeviceStatusLoaded state) {
return Flexible(
child: ListTile(
contentPadding: EdgeInsets.zero,
title: Text(
label,
style: context.textTheme.bodySmall!.copyWith(
fontSize: 13,
color: ColorsManager.blackColor,
),
),
leading: Radio<ScheduleModes>(
value: mode,
groupValue: state.scheduleMode,
onChanged: (ScheduleModes? value) {
if (value != null) {
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
scheduleMode: value,
hours: state.hours ?? 0,
minutes: state.minutes ?? 0,
));
}
},
),
),
);
}
Widget _buildScheduleManagementUI(WaterHeaterDeviceStatusLoaded state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 170,
height: 40,
child: DefaultButton(
borderColor: ColorsManager.boxColor,
padding: 2,
backgroundColor: ColorsManager.graysColor,
borderRadius: 15,
onPressed: () =>
_showAddScheduleDialog(context, schedule: null, index: null),
child: Row(
children: [
const Icon(Icons.add, color: ColorsManager.primaryColor),
Text(
' Add new schedule',
style: context.textTheme.bodySmall!.copyWith(
color: ColorsManager.blackColor,
),
),
],
),
),
),
const SizedBox(height: 20),
_buildScheduleTable(state),
],
);
}
Widget _buildScheduleTable(WaterHeaterDeviceStatusLoaded state) {
return Column(
children: [
Table(
border: TableBorder.all(
color: ColorsManager.graysColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20), topRight: Radius.circular(20)),
),
children: [
TableRow(
decoration: const BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20),
),
),
children: [
_buildTableHeader('Active'),
_buildTableHeader('Days'),
_buildTableHeader('Time'),
_buildTableHeader('Function'),
_buildTableHeader('Action'),
],
),
],
),
Container(
height: 200,
decoration: BoxDecoration(
border: Border.all(color: ColorsManager.graysColor),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20)),
),
child: state.schedules.isEmpty
? _buildEmptyState(context)
: _buildTableBody(state),
),
],
);
}
Widget _buildEmptyState(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(Assets.emptyRecords, width: 40, height: 40),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'No schedules added yet',
style: context.textTheme.bodySmall!.copyWith(
fontSize: 13,
color: ColorsManager.grayColor,
),
),
),
],
),
);
}
Widget _buildTableBody(WaterHeaterDeviceStatusLoaded state) {
return SingleChildScrollView(
child: Table(
border: TableBorder.all(color: ColorsManager.graysColor),
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: [
for (int i = 0; i < state.schedules.length; i++)
_buildScheduleRow(state.schedules[i], i, context),
],
),
);
}
TableRow _buildScheduleRow(
ScheduleEntry schedule, int index, BuildContext context) {
return TableRow(
children: [
Center(
child: schedule.functionOn
? const Icon(Icons.radio_button_checked,
color: ColorsManager.blueColor)
: const Icon(Icons.radio_button_unchecked)),
Center(child: Text(_getSelectedDays(schedule.selectedDays))),
Center(child: Text(schedule.time.format(context))),
Center(child: Text(schedule.functionOn ? 'On' : 'Off')),
Center(
child: Wrap(
runAlignment: WrapAlignment.center,
children: [
TextButton(
style: TextButton.styleFrom(padding: EdgeInsets.zero),
onPressed: () {
_showAddScheduleDialog(context,
schedule: schedule, index: index);
},
child: Text(
'Edit',
style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.blueColor),
),
),
TextButton(
style: TextButton.styleFrom(padding: EdgeInsets.zero),
onPressed: () {
context
.read<WaterHeaterBloc>()
.add(DeleteScheduleEvent(index));
},
child: Text(
'Delete',
style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.blueColor),
),
),
],
),
),
],
);
}
String _getSelectedDays(List<bool> selectedDays) {
final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
List<String> selectedDaysStr = [];
for (int i = 0; i < selectedDays.length; i++) {
if (selectedDays[i]) {
selectedDaysStr.add(days[i]);
}
}
return selectedDaysStr.join(', ');
}
Widget _buildTableHeader(String label) {
return TableCell(
child: Padding(
padding: const EdgeInsets.all(12),
child: Text(
label,
style: context.textTheme.bodySmall!.copyWith(
fontSize: 13,
color: ColorsManager.grayColor,
),
),
),
);
}
void _showAddScheduleDialog(BuildContext context,
{ScheduleEntry? schedule, int? index}) {
final bloc = context.read<WaterHeaterBloc>();
if (schedule != null) {
bloc.add(InitializeAddScheduleEvent(
selectedTime: schedule.time,
selectedDays: schedule.selectedDays,
functionOn: schedule.functionOn,
isEditing: true,
index: index,
));
} else {
bloc.add(
const InitializeAddScheduleEvent(
selectedDays: [false, false, false, false, false, false, false],
functionOn: false,
isEditing: false,
index: null,
selectedTime: null,
),
);
}
showDialog(
context: context,
builder: (ctx) {
return BlocProvider.value(
value: bloc,
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
builder: (context, state) {
if (state is WaterHeaterDeviceStatusLoaded) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SizedBox(),
Text(
'Scheduling',
style: context.textTheme.titleLarge!.copyWith(
color: ColorsManager.dialogBlueTitle,
fontWeight: FontWeight.bold,
),
),
const SizedBox(),
],
),
const SizedBox(height: 24),
SizedBox(
width: 150,
height: 40,
child: DefaultButton(
padding: 8,
backgroundColor: ColorsManager.boxColor,
borderRadius: 15,
onPressed: () async {
TimeOfDay? time = await showTimePicker(
context: context,
initialTime:
state.selectedTime ?? TimeOfDay.now(),
);
if (time != null) {
bloc.add(UpdateSelectedTimeEvent(time));
}
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
state.selectedTime == null
? 'Time'
: state.selectedTime!.format(context),
style: context.textTheme.bodySmall!.copyWith(
color: ColorsManager.grayColor,
),
),
const Icon(
Icons.access_time,
color: ColorsManager.grayColor,
size: 18,
),
],
),
),
),
const SizedBox(height: 16),
_buildDayCheckboxes(context, state.selectedDays),
const SizedBox(height: 16),
_buildFunctionSwitch(context, state.functionOn),
],
),
actions: [
SizedBox(
width: 200,
child: DefaultButton(
height: 40,
onPressed: () {
Navigator.pop(context);
},
backgroundColor: ColorsManager.boxColor,
child: Text(
'Cancel',
style: context.textTheme.bodyMedium,
),
),
),
SizedBox(
width: 200,
child: DefaultButton(
height: 40,
onPressed: () {
if (state.selectedTime != null) {
if (state.isEditing && index != null) {
bloc.add(UpdateScheduleEntryEvent(
deviceId: state.status.uuid,
category: 'kg',
functionOn: state.functionOn,
));
} else {
bloc.add(AddScheduleEvent(
category: 'kg',
time: state.selectedTime!,
selectedDays: state.selectedDays,
functionOn: state.functionOn,
));
}
Navigator.pop(context);
}
},
backgroundColor: ColorsManager.primaryColor,
child: const Text('Save'),
),
),
],
);
}
return const SizedBox();
},
),
);
},
);
}
Widget _buildDayCheckboxes(BuildContext context, List<bool> selectedDays) {
return Row(
children: List.generate(7, (index) {
final dayLabels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
return Row(
children: [
Checkbox(
value: selectedDays[index],
onChanged: (bool? value) {
context
.read<WaterHeaterBloc>()
.add(UpdateSelectedDayEvent(index, value!));
},
),
Text(dayLabels[index]),
],
);
}),
);
}
Widget _buildFunctionSwitch(BuildContext context, bool isOn) {
return Row(
children: [
Text(
'Function:',
style: context.textTheme.bodySmall!
.copyWith(color: ColorsManager.grayColor),
),
const SizedBox(width: 10),
Radio<bool>(
value: true,
groupValue: isOn,
onChanged: (bool? value) {
context
.read<WaterHeaterBloc>()
.add(const UpdateFunctionOnEvent(true));
},
),
const Text('On'),
const SizedBox(width: 10),
Radio<bool>(
value: false,
groupValue: isOn,
onChanged: (bool? value) {
context
.read<WaterHeaterBloc>()
.add(const UpdateFunctionOnEvent(false));
},
),
const Text('Off'),
],
);
}
Center _buildSaveStopCancelButtons(
BuildContext context, WaterHeaterDeviceStatusLoaded state) {
return Center(
child: SizedBox(
width: 400,
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Expanded(
child: DefaultButton(
height: 40,
onPressed: () {
Navigator.pop(context);
},
backgroundColor: ColorsManager.boxColor,
child: Text(
'Cancel',
style: context.textTheme.bodyMedium,
),
),
),
const SizedBox(width: 20),
Expanded(
child:
(state.countdownRemaining != null && state.isActive == true)
? DefaultButton(
height: 40,
onPressed: () {
late String code;
if (state.scheduleMode == ScheduleModes.countdown) {
code = 'countdown_1';
} else if (state.scheduleMode ==
ScheduleModes.inching) {
code = 'switch_inching';
}
context
.read<WaterHeaterBloc>()
.add(StopScheduleEvent(widget.status.uuid));
context.read<WaterHeaterBloc>().add(
ToggleWaterHeaterEvent(
deviceId: widget.status.uuid,
code: code,
value: 0,
),
);
},
backgroundColor: Colors.red,
child: const Text('Stop'),
)
: DefaultButton(
height: 40,
onPressed: () {
late String code;
if (state.scheduleMode == ScheduleModes.countdown) {
code = 'countdown_1';
} else if (state.scheduleMode ==
ScheduleModes.inching) {
code = 'switch_inching';
}
context.read<WaterHeaterBloc>().add(
ToggleWaterHeaterEvent(
deviceId: widget.status.uuid,
code: code,
value: Duration(
hours: state.hours ?? 0,
minutes: state.minutes ?? 0)
.inSeconds,
),
);
},
backgroundColor: ColorsManager.primaryColor,
child: const Text('Save'),
),
),
],
),
),
);
}
List<Widget> _buildCountDownAngInchingView(
BuildContext context, WaterHeaterDeviceStatusLoaded state) {
final isCountDown =
state.scheduleMode?.name == ScheduleModes.countdown.name;
return [
Text(
isCountDown ? 'Countdown:' : 'Inching:',
style: context.textTheme.bodySmall!.copyWith(
fontSize: 13,
color: ColorsManager.grayColor,
),
),
const SizedBox(height: 8),
Visibility(
visible: !isCountDown,
child: const Text(
'Once enabled this feature, each time the device is turned on, it will automatically turn of after a period time as pre-set.'),
),
const SizedBox(height: 8),
_hourMinutesWheel(state, context)
];
}
Row _hourMinutesWheel(
WaterHeaterDeviceStatusLoaded state, BuildContext context) {
final isActive =
(state.countdownRemaining != null && state.isActive == true);
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_buildPickerColumn(context, 'h', state.hours ?? 0, 24, (value) {
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
scheduleMode: state.scheduleMode ?? ScheduleModes.countdown,
hours: value,
minutes: state.minutes ?? 0,
));
}, isActive: isActive),
const SizedBox(width: 10),
_buildPickerColumn(context, 'm', state.minutes ?? 0, 60, (value) {
context.read<WaterHeaterBloc>().add(UpdateScheduleEvent(
scheduleMode: state.scheduleMode ?? ScheduleModes.countdown,
hours: state.hours ?? 0,
minutes: value,
));
}, isActive: isActive),
],
);
}
Widget _buildPickerColumn(BuildContext context, String label,
int initialValue, int itemCount, ValueChanged<int> onSelected,
{required bool isActive}) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 40,
width: 80,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(8),
),
child: ListWheelScrollView.useDelegate(
key: ValueKey('$label-$initialValue'),
controller: FixedExtentScrollController(
initialItem: initialValue,
),
itemExtent: 40.0,
physics: const FixedExtentScrollPhysics(),
onSelectedItemChanged: onSelected,
childDelegate: ListWheelChildBuilderDelegate(
builder: (context, index) {
return Center(
child: Text(
index.toString().padLeft(2, '0'),
style: TextStyle(
fontSize: 24,
color: isActive ? ColorsManager.grayColor : Colors.black,
),
),
);
},
childCount: itemCount,
),
),
),
const SizedBox(width: 8),
Text(
label,
style: const TextStyle(
color: ColorsManager.grayColor,
fontSize: 18,
),
),
],
);
}
}