Merge pull request #27 from SyncrowIOT/schedual_view

Schedual view
This commit is contained in:
Abdullah
2024-09-24 12:31:39 +03:00
committed by GitHub
12 changed files with 345 additions and 230 deletions

View File

@ -35,25 +35,15 @@ class TwoGangDeviceControlView extends StatelessWidget
}
Widget _buildStatusControls(BuildContext context, TwoGangStatusModel status) {
final isExtraLarge = isExtraLargeScreenSize(context);
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return GridView(
padding: const EdgeInsets.symmetric(horizontal: 50),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isLarge || isExtraLarge
? 3
: isMedium
? 2
: 1,
mainAxisExtent: 140,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
return Center(
child: Wrap(
alignment: WrapAlignment.center,
spacing: 12,
runSpacing: 12,
children: [
ToggleWidget(
SizedBox(
width: 200,
child: ToggleWidget(
value: status.switch1,
code: 'switch_1',
deviceId: deviceId,
@ -66,7 +56,10 @@ class TwoGangDeviceControlView extends StatelessWidget
));
},
),
ToggleWidget(
),
SizedBox(
width: 200,
child: ToggleWidget(
value: status.switch2,
code: 'switch_2',
deviceId: deviceId,
@ -79,7 +72,9 @@ class TwoGangDeviceControlView extends StatelessWidget
));
},
),
),
],
),
);
}
}

View File

@ -5,6 +5,7 @@ import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
@ -487,10 +488,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
emit(ScheduleLoadingState());
try {
// List<ScheduleModel> schedules = await DevicesManagementApi()
// .getDeviceSchedules(deviceStatus.uuid, event.category);
List<ScheduleModel> schedules = const [];
List<ScheduleModel> schedules = await DevicesManagementApi()
.getDeviceSchedules(deviceStatus.uuid, event.category);
emit(WaterHeaterDeviceStatusLoaded(
deviceStatus,
@ -513,7 +512,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded;
ScheduleModel newSchedule = ScheduleModel(
ScheduleEntry newSchedule = ScheduleEntry(
category: event.category,
time: formatTimeOfDayToISO(event.time),
function: Status(code: 'switch_1', value: event.functionOn),
@ -526,10 +525,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
.addScheduleRecord(newSchedule, currentState.status.uuid);
if (success) {
final updatedSchedules =
List<ScheduleModel>.from(currentState.schedules)..add(newSchedule);
emit(currentState.copyWith(schedules: updatedSchedules));
add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid));
} else {
emit(currentState);
//emit(const WaterHeaterFailedState(error: 'Failed to add schedule.'));
@ -544,23 +540,23 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded;
ScheduleModel updatedSchedule = currentState.schedules[event.index]
.copyWith(
function: Status(code: 'switch_1', value: event.functionOn));
// emit(ScheduleLoadingState());
final updatedSchedules = currentState.schedules.map((schedule) {
if (schedule.scheduleId == event.scheduleId) {
return schedule.copyWith(
function: Status(code: 'switch_1', value: event.functionOn),
enable: event.enable,
);
}
return schedule;
}).toList();
bool success = await DevicesManagementApi().updateScheduleRecord(
enable: event.functionOn,
enable: event.enable,
uuid: currentState.status.uuid,
scheduleId: event.scheduleId,
);
if (success) {
final updatedSchedules =
List<ScheduleModel>.from(currentState.schedules)
..[event.index] = updatedSchedule;
emit(currentState.copyWith(schedules: updatedSchedules));
} else {
emit(currentState);
@ -582,9 +578,9 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
.deleteScheduleRecord(currentState.status.uuid, event.scheduleId);
if (success) {
final updatedSchedules =
List<ScheduleModel>.from(currentState.schedules)
..removeAt(event.index);
final updatedSchedules = currentState.schedules
.where((schedule) => schedule.scheduleId != event.scheduleId)
.toList();
emit(currentState.copyWith(schedules: updatedSchedules));
} else {

View File

@ -82,23 +82,22 @@ final class DeleteScheduleEvent extends WaterHeaterEvent {
}
final class UpdateScheduleEntryEvent extends WaterHeaterEvent {
final bool functionOn;
final String category;
final bool enable;
final dynamic functionOn;
final String deviceId;
final int index;
final String scheduleId;
const UpdateScheduleEntryEvent({
required this.enable,
required this.functionOn,
required this.category,
required this.deviceId,
required this.scheduleId,
required this.index,
});
@override
List<Object?> get props =>
[category, functionOn, deviceId, scheduleId, index];
List<Object?> get props => [enable, deviceId, scheduleId];
}
class GetSchedulesEvent extends WaterHeaterEvent {

View File

@ -118,7 +118,7 @@ class ScheduleDialogHelper {
_buildDayCheckboxes(context, state.selectedDays,
isEdit: isEdit),
const SizedBox(height: 16),
_buildFunctionSwitch(context, state.functionOn),
_buildFunctionSwitch(context, state.functionOn, isEdit),
],
),
actions: [
@ -143,16 +143,10 @@ class ScheduleDialogHelper {
onPressed: () {
if (state.selectedTime != null) {
if (state.isEditing && index != null) {
bloc.add(UpdateScheduleEntryEvent(
index: index,
deviceId: state.status.uuid,
category: 'kg',
functionOn: state.functionOn,
scheduleId: state.schedules[index].scheduleId,
));
return;
} else {
bloc.add(AddScheduleEvent(
category: 'kg',
category: 'switch_1',
time: state.selectedTime!,
selectedDays: state.selectedDays,
functionOn: state.functionOn,
@ -177,8 +171,15 @@ class ScheduleDialogHelper {
}
static TimeOfDay _convertStringToTimeOfDay(String timeString) {
final DateTime dateTime = DateTime.parse(timeString);
return TimeOfDay(hour: dateTime.hour, minute: dateTime.minute);
final regex = RegExp(r'^(\d{2}):(\d{2})$');
final match = regex.firstMatch(timeString);
if (match != null) {
final hour = int.parse(match.group(1)!);
final minute = int.parse(match.group(2)!);
return TimeOfDay(hour: hour, minute: minute);
} else {
throw const FormatException('Invalid time format');
}
}
static List<bool> _convertDaysStringToBooleans(List<String> selectedDays) {
@ -220,7 +221,8 @@ class ScheduleDialogHelper {
);
}
static Widget _buildFunctionSwitch(BuildContext context, bool isOn) {
static Widget _buildFunctionSwitch(
BuildContext context, bool isOn, bool? isEdit) {
return Row(
children: [
Text(
@ -233,9 +235,13 @@ class ScheduleDialogHelper {
value: true,
groupValue: isOn,
onChanged: (bool? value) {
if (isEdit == true) {
return;
} else {
context
.read<WaterHeaterBloc>()
.add(const UpdateFunctionOnEvent(true));
}
},
),
const Text('On'),
@ -244,9 +250,13 @@ class ScheduleDialogHelper {
value: false,
groupValue: isOn,
onChanged: (bool? value) {
if (isEdit == true) {
return;
} else {
context
.read<WaterHeaterBloc>()
.add(const UpdateFunctionOnEvent(false));
}
},
),
const Text('Off'),

View File

@ -1,19 +1,80 @@
// import 'package:flutter/material.dart';
import 'dart:convert';
// class ScheduleEntry {
// final List<bool> selectedDays;
// final TimeOfDay time;
// final bool functionOn;
// final String category;
import 'package:flutter/foundation.dart';
// ScheduleEntry({
// required this.selectedDays,
// required this.time,
// required this.functionOn,
// required this.category,
// });
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
// @override
// String toString() =>
// 'ScheduleEntry(selectedDays: $selectedDays, time: $time, functionOn: $functionOn)';
// }
class ScheduleEntry {
final String category;
final String time;
final Status function;
final List<String> days;
ScheduleEntry({
required this.category,
required this.time,
required this.function,
required this.days,
});
@override
String toString() {
return 'ScheduleEntry(category: $category, time: $time, function: $function, days: $days)';
}
ScheduleEntry copyWith({
String? category,
String? time,
Status? function,
List<String>? days,
}) {
return ScheduleEntry(
category: category ?? this.category,
time: time ?? this.time,
function: function ?? this.function,
days: days ?? this.days,
);
}
Map<String, dynamic> toMap() {
return {
'category': category,
'time': time,
'function': function.toMap(),
'days': days,
};
}
factory ScheduleEntry.fromMap(Map<String, dynamic> map) {
return ScheduleEntry(
category: map['category'] ?? '',
time: map['time'] ?? '',
function: Status.fromMap(map['function']),
days: List<String>.from(map['days']),
);
}
String toJson() => json.encode(toMap());
factory ScheduleEntry.fromJson(String source) =>
ScheduleEntry.fromMap(json.decode(source));
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ScheduleEntry &&
other.category == category &&
other.time == time &&
other.function == function &&
listEquals(other.days, days);
}
@override
int get hashCode {
return category.hashCode ^
time.hashCode ^
function.hashCode ^
days.hashCode;
}
}

View File

@ -9,25 +9,30 @@ class ScheduleModel {
final String time;
final Status function;
final List<String> days;
final TimeOfDay? timeOfDay;
final List<bool>? selectedDays;
final String timezoneId;
final bool enable;
ScheduleModel({
required this.scheduleId,
required this.category,
required this.time,
required this.function,
required this.days,
this.timeOfDay,
this.selectedDays,
this.scheduleId = '',
required this.timezoneId,
required this.enable,
});
Map<String, dynamic> toMap() {
return {
'scheduleId': scheduleId,
'category': category,
'time': time,
'function': function.toMap(),
'days': days,
'timezoneId': timezoneId,
'enable': enable,
};
}
@ -37,11 +42,11 @@ class ScheduleModel {
category: map['category'] ?? '',
time: map['time'] ?? '',
function: Status.fromMap(map['function']),
days: List<String>.from(map['days']),
timeOfDay:
parseTimeOfDay(map['time']),
selectedDays:
parseSelectedDays(map['days']),
days: List<String>.from(map['days'].map((e) => e.toString())),
timezoneId: map['timezoneId'] ?? '',
enable: map['enable'] ?? false,
selectedDays: parseSelectedDays(
List<String>.from(map['days'].map((e) => e.toString()))),
);
}
@ -51,22 +56,24 @@ class ScheduleModel {
ScheduleModel.fromMap(json.decode(source));
ScheduleModel copyWith({
String? scheduleId,
String? category,
String? time,
Status? function,
List<String>? days,
TimeOfDay? timeOfDay,
List<bool>? selectedDays,
String? scheduleId,
String? timezoneId,
bool? enable,
}) {
return ScheduleModel(
scheduleId: scheduleId ?? this.scheduleId,
category: category ?? this.category,
time: time ?? this.time,
function: function ?? this.function,
days: days ?? this.days,
timeOfDay: timeOfDay ?? this.timeOfDay,
selectedDays: selectedDays ?? this.selectedDays,
scheduleId: scheduleId ?? this.scheduleId,
timezoneId: timezoneId ?? this.timezoneId,
enable: enable ?? this.enable,
);
}
@ -97,7 +104,7 @@ class ScheduleModel {
@override
String toString() {
return 'ScheduleModel(category: $category, time: $time, function: $function, days: $days, timeOfDay: $timeOfDay, selectedDays: $selectedDays)';
return 'ScheduleModel(category: $category, time: $time, function: $function, days: $days, selectedDays: $selectedDays)';
}
@override
@ -109,7 +116,7 @@ class ScheduleModel {
other.time == time &&
other.function == function &&
listEquals(other.days, days) &&
timeOfDay == other.timeOfDay &&
// timeOfDay == other.timeOfDay &&
listEquals(other.selectedDays, selectedDays);
}
@ -119,7 +126,7 @@ class ScheduleModel {
time.hashCode ^
function.hashCode ^
days.hashCode ^
timeOfDay.hashCode ^
// timeOfDay.hashCode ^
selectedDays.hashCode;
}
}

View File

@ -56,8 +56,7 @@ class _BuildScheduleViewState extends State<BuildScheduleView> {
context,
schedule: null,
index: null,
isEdit: false
),
isEdit: false),
),
if (state.scheduleMode == ScheduleModes.countdown ||
state.scheduleMode == ScheduleModes.inching)
@ -80,15 +79,31 @@ class _BuildScheduleViewState extends State<BuildScheduleView> {
if (state.scheduleMode != ScheduleModes.countdown &&
state.scheduleMode != ScheduleModes.inching)
ScheduleModeButtons(
onSave: () {},
onSave: () {
Navigator.pop(context);
},
),
],
);
}
if (state is WaterHeaterLoadingState) {
return const Center(child: CircularProgressIndicator());
return const SizedBox(
height: 200,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ScheduleHeader(),
SizedBox(
height: 20,
),
Center(child: CircularProgressIndicator()),
],
));
}
return const SizedBox();
return const SizedBox(
height: 200,
child: ScheduleHeader(),
);
},
),
),

View File

@ -72,7 +72,7 @@ class ScheduleModeSelector extends StatelessWidget {
if (value == ScheduleModes.schedule) {
context.read<WaterHeaterBloc>().add(
GetSchedulesEvent(
category: 'kg',
category: 'switch_1',
uuid: state.status.uuid,
),
);

View File

@ -1,76 +1,76 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
import 'package:syncrow_web/utils/format_date_time.dart';
import 'package:syncrow_web/utils/color_manager.dart';
// import 'package:flutter/material.dart';
// import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
// import 'package:syncrow_web/utils/format_date_time.dart';
// import 'package:syncrow_web/utils/color_manager.dart';
class ScheduleRowWidget extends StatelessWidget {
final ScheduleModel schedule;
final int index;
final Function onEdit;
final Function onDelete;
// class ScheduleRowWidget extends StatelessWidget {
// final ScheduleModel schedule;
// final int index;
// final Function onEdit;
// final Function onDelete;
const ScheduleRowWidget({
super.key,
required this.schedule,
required this.index,
required this.onEdit,
required this.onDelete,
});
// const ScheduleRowWidget({
// super.key,
// required this.schedule,
// required this.index,
// required this.onEdit,
// required this.onDelete,
// });
@override
Widget build(BuildContext context) {
return Table(
border: TableBorder.all(color: ColorsManager.graysColor),
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
children: [
TableRow(
children: [
Center(
child: schedule.function.value
? const Icon(Icons.radio_button_checked,
color: ColorsManager.blueColor)
: const Icon(Icons.radio_button_unchecked),
),
Center(child: Text(_getSelectedDays(schedule.selectedDays ?? []))),
Center(child: Text(formatIsoStringToTime(schedule.time))),
Center(child: Text(schedule.function.value ? 'On' : 'Off')),
Center(
child: Wrap(
runAlignment: WrapAlignment.center,
children: [
TextButton(
style: TextButton.styleFrom(padding: EdgeInsets.zero),
onPressed: () => onEdit(),
child: const Text(
'Edit',
style: TextStyle(color: ColorsManager.blueColor),
),
),
TextButton(
style: TextButton.styleFrom(padding: EdgeInsets.zero),
onPressed: () => onDelete(),
child: const Text(
'Delete',
style: TextStyle(color: ColorsManager.blueColor),
),
),
],
),
),
],
),
],
);
}
// @override
// Widget build(BuildContext context) {
// return Table(
// border: TableBorder.all(color: ColorsManager.graysColor),
// defaultVerticalAlignment: TableCellVerticalAlignment.middle,
// children: [
// TableRow(
// children: [
// Center(
// child: schedule.enable
// ? const Icon(Icons.radio_button_checked,
// color: ColorsManager.blueColor)
// : const Icon(Icons.radio_button_unchecked),
// ),
// Center(child: Text(_getSelectedDays(schedule.selectedDays ?? []))),
// Center(child: Text(formatIsoStringToTime(schedule.time, context))),
// Center(child: Text(schedule.enable ? 'On' : 'Off')),
// Center(
// child: Wrap(
// runAlignment: WrapAlignment.center,
// children: [
// TextButton(
// style: TextButton.styleFrom(padding: EdgeInsets.zero),
// onPressed: () => onEdit(),
// child: const Text(
// 'Edit',
// style: TextStyle(color: ColorsManager.blueColor),
// ),
// ),
// TextButton(
// style: TextButton.styleFrom(padding: EdgeInsets.zero),
// onPressed: () => onDelete(),
// child: const Text(
// 'Delete',
// style: TextStyle(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(', ');
}
}
// 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(', ');
// }
// }

View File

@ -68,7 +68,9 @@ class ScheduleTableWidget extends StatelessWidget {
),
child: _buildTableBody(state, context));
}
return const SizedBox();
return const SizedBox(
height: 200,
);
},
),
],
@ -107,15 +109,18 @@ class ScheduleTableWidget extends StatelessWidget {
Widget _buildTableBody(
WaterHeaterDeviceStatusLoaded state, BuildContext context) {
return SingleChildScrollView(
return SizedBox(
height: 200,
child: 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),
_buildScheduleRow(state.schedules[i], i, context, state),
],
),
),
);
}
@ -134,19 +139,38 @@ class ScheduleTableWidget extends StatelessWidget {
);
}
TableRow _buildScheduleRow(
ScheduleModel schedule, int index, BuildContext context) {
TableRow _buildScheduleRow(ScheduleModel schedule, int index,
BuildContext context, WaterHeaterDeviceStatusLoaded state) {
return TableRow(
children: [
Center(
child: schedule.function.value
child: GestureDetector(
onTap: () {
context.read<WaterHeaterBloc>().add(UpdateScheduleEntryEvent(
index: index,
enable: !schedule.enable,
scheduleId: schedule.scheduleId,
deviceId: state.status.uuid,
functionOn: schedule.function.value,
));
},
child: SizedBox(
width: 24,
height: 24,
child: schedule.enable
? const Icon(Icons.radio_button_checked,
color: ColorsManager.blueColor)
: const Icon(Icons.radio_button_unchecked)),
: const Icon(
Icons.radio_button_unchecked,
color: ColorsManager.grayColor,
),
),
),
),
Center(
child: Text(_getSelectedDays(
ScheduleModel.parseSelectedDays(schedule.days)))),
Center(child: Text(formatIsoStringToTime(schedule.time))),
Center(child: Text(formatIsoStringToTime(schedule.time, context))),
Center(child: Text(schedule.function.value ? 'On' : 'Off')),
Center(
child: Wrap(

View File

@ -3,6 +3,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_rep
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
import 'package:syncrow_web/pages/visitor_password/model/device_model.dart';
import 'package:syncrow_web/services/api/http_service.dart';
@ -179,7 +180,7 @@ class DevicesManagementApi {
}
Future<bool> addScheduleRecord(
ScheduleModel sendSchedule, String uuid) async {
ScheduleEntry sendSchedule, String uuid) async {
try {
final response = await HTTPService().post(
path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid),
@ -200,14 +201,14 @@ class DevicesManagementApi {
String uuid, String category) async {
try {
final response = await HTTPService().get(
path: ApiEndpoints.scheduleByDeviceId
path: ApiEndpoints.getScheduleByDeviceId
.replaceAll('{deviceUuid}', uuid)
.replaceAll('{category}', category),
showServerMessage: true,
expectedResponseModel: (json) {
List<ScheduleModel> schedules = [];
for (var schedule in json['schedules']) {
schedules.add(ScheduleModel.fromJson(schedule));
for (var schedule in json) {
schedules.add(ScheduleModel.fromMap(schedule));
}
return schedules;
},
@ -225,7 +226,7 @@ class DevicesManagementApi {
required String scheduleId}) async {
try {
final response = await HTTPService().put(
path: ApiEndpoints.scheduleByDeviceId
path: ApiEndpoints.updateScheduleByDeviceId
.replaceAll('{deviceUuid}', uuid)
.replaceAll('{scheduleUuid}', scheduleId),
body: {
@ -246,7 +247,7 @@ class DevicesManagementApi {
Future<bool> deleteScheduleRecord(String uuid, String scheduleId) async {
try {
final response = await HTTPService().delete(
path: ApiEndpoints.scheduleByDeviceId
path: ApiEndpoints.deleteScheduleByDeviceId
.replaceAll('{deviceUuid}', uuid)
.replaceAll('{scheduleUuid}', scheduleId),
showServerMessage: true,

View File

@ -25,7 +25,14 @@ String formatTimeOfDayToISO(TimeOfDay time, {DateTime? currentDate}) {
return dateTime.toUtc().toIso8601String();
}
String formatIsoStringToTime(String isoString) {
final dateTime = DateTime.parse(isoString);
return DateFormat('hh:mm a').format(dateTime);
String formatIsoStringToTime(String isoString, BuildContext context) {
try {
final parts = isoString.split(':');
final hour = int.parse(parts[0]);
final minute = int.parse(parts[1]);
final timeOfDay = TimeOfDay(hour: hour, minute: minute);
return timeOfDay.format(context);
} catch (e) {
return isoString;
}
}