From c3d8e6a52ed419293612a1a029cf4c745c7b0af8 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 11 Sep 2024 00:27:51 +0300 Subject: [PATCH 01/65] add batch controll box selection logic --- .../ac/view/ac_device_control.dart | 6 +- .../bloc/device_managment_bloc.dart | 31 +++++-- .../bloc/device_managment_state.dart | 14 +++- .../helper/route_controls_based_code.dart | 41 +++++++++- .../all_devices/models/devices_model.dart | 62 ++++++++++++++ .../widgets/device_managment_body.dart | 49 ++++++++--- .../curtain/view/curtain_status_view.dart | 5 +- .../door_lock/view/door_lock_status_view.dart | 4 +- .../shared/device_batch_control_dialog.dart | 81 +++++++++++++++++++ .../bloc/living_room_bloc.dart | 34 ++++++-- .../bloc/living_room_event.dart | 10 +++ .../view/living_room_batch_controls.dart | 67 +++++++++++++++ .../view/living_room_device_control.dart | 15 ++-- pubspec.lock | 24 +++--- 14 files changed, 383 insertions(+), 60 deletions(-) create mode 100644 lib/pages/device_managment/shared/device_batch_control_dialog.dart create mode 100644 lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index c2b554b9..123ffdfd 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -11,8 +11,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout { - const AcDeviceControl({super.key, required this.device}); +class AcDeviceControls extends StatelessWidget with HelperResponsiveLayout { + const AcDeviceControls({super.key, required this.device}); final AllDevicesModel device; @@ -69,7 +69,7 @@ class AcDeviceControl extends StatelessWidget with HelperResponsiveLayout { deviceId: device.uuid!, description: 'Child Lock', icon: - state.status.childLock ? Assets.childLock:Assets.unlock , + state.status.childLock ? Assets.childLock : Assets.unlock, ), ], ); diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart index 37447cc3..1919cb49 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart @@ -41,6 +41,7 @@ class DeviceManagementBloc offlineCount: _offlineCount, lowBatteryCount: _lowBatteryCount, selectedDevice: null, + isControlButtonEnabled: false, )); } catch (e) { emit(DeviceManagementInitial()); @@ -69,8 +70,8 @@ class DeviceManagementBloc onlineCount: _onlineCount, offlineCount: _offlineCount, lowBatteryCount: _lowBatteryCount, - selectedDevice: - _selectedDevices.isNotEmpty ? _selectedDevices.first : null, + selectedDevice: _selectedDevices.isNotEmpty ? _selectedDevices : null, + isControlButtonEnabled: _selectedDevices.isNotEmpty, )); if (productName.isNotEmpty) { @@ -92,6 +93,7 @@ class DeviceManagementBloc offlineCount: _offlineCount, lowBatteryCount: _lowBatteryCount, selectedDevice: null, + isControlButtonEnabled: false, )); } @@ -111,7 +113,19 @@ class DeviceManagementBloc _selectedDevices.add(event.selectedDevice); } - bool isControlButtonEnabled = _selectedDevices.length == 1; + List clonedSelectedDevices = List.from(_selectedDevices); + + bool isControlButtonEnabled = false; + + if (clonedSelectedDevices.length == 1) { + isControlButtonEnabled = true; + } else if (clonedSelectedDevices.length > 1) { + + final productTypes = + clonedSelectedDevices.map((device) => device.productType).toSet(); + isControlButtonEnabled = productTypes.length == + 1; + } if (state is DeviceManagementLoaded) { emit(DeviceManagementLoaded( @@ -120,7 +134,9 @@ class DeviceManagementBloc onlineCount: _onlineCount, offlineCount: _offlineCount, lowBatteryCount: _lowBatteryCount, - selectedDevice: isControlButtonEnabled ? _selectedDevices.first : null, + selectedDevice: + clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null, + isControlButtonEnabled: isControlButtonEnabled, )); } else if (state is DeviceManagementFiltered) { emit(DeviceManagementFiltered( @@ -129,7 +145,9 @@ class DeviceManagementBloc onlineCount: _onlineCount, offlineCount: _offlineCount, lowBatteryCount: _lowBatteryCount, - selectedDevice: isControlButtonEnabled ? _selectedDevices.first : null, + selectedDevice: + clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null, + isControlButtonEnabled: isControlButtonEnabled, )); } } @@ -158,12 +176,10 @@ class DeviceManagementBloc void _onSearchDevices( SearchDevices event, Emitter emit) { - // If the search fields are all empty, restore the last filtered devices if ((event.community == null || event.community!.isEmpty) && (event.unitName == null || event.unitName!.isEmpty) && (event.productName == null || event.productName!.isEmpty)) { productName = ''; - // If the current state is filtered, re-emit the filtered state if (state is DeviceManagementFiltered) { add(FilterDevices(_getFilterFromIndex(_selectedIndex))); } @@ -214,6 +230,7 @@ class DeviceManagementBloc offlineCount: _offlineCount, lowBatteryCount: _lowBatteryCount, selectedDevice: null, + isControlButtonEnabled: false, )); } } diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_state.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_state.dart index 9a6e2f41..f7513890 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_managment_state.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_state.dart @@ -17,7 +17,8 @@ class DeviceManagementLoaded extends DeviceManagementState { final int onlineCount; final int offlineCount; final int lowBatteryCount; - final AllDevicesModel? selectedDevice; + final List? selectedDevice; + final bool isControlButtonEnabled; const DeviceManagementLoaded({ required this.devices, @@ -26,6 +27,7 @@ class DeviceManagementLoaded extends DeviceManagementState { required this.offlineCount, required this.lowBatteryCount, this.selectedDevice, + required this.isControlButtonEnabled, }); @override @@ -35,7 +37,8 @@ class DeviceManagementLoaded extends DeviceManagementState { onlineCount, offlineCount, lowBatteryCount, - selectedDevice + selectedDevice, + isControlButtonEnabled ]; } @@ -45,7 +48,8 @@ class DeviceManagementFiltered extends DeviceManagementState { final int onlineCount; final int offlineCount; final int lowBatteryCount; - final AllDevicesModel? selectedDevice; + final List? selectedDevice; + final bool isControlButtonEnabled; const DeviceManagementFiltered({ required this.filteredDevices, @@ -54,6 +58,7 @@ class DeviceManagementFiltered extends DeviceManagementState { required this.offlineCount, required this.lowBatteryCount, this.selectedDevice, + required this.isControlButtonEnabled, }); @override @@ -63,7 +68,8 @@ class DeviceManagementFiltered extends DeviceManagementState { onlineCount, offlineCount, lowBatteryCount, - selectedDevice + selectedDevice, + isControlButtonEnabled ]; } diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 3b228192..5df77b2b 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_s import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_status_view.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; +import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_device_control.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart'; @@ -13,7 +14,7 @@ mixin RouteControlsBasedCode { Widget routeControlsWidgets({required AllDevicesModel device}) { switch (device.productType) { case '3G': - return LivingRoomDeviceControl( + return LivingRoomDeviceControls( deviceId: device.uuid!, ); case 'GW': @@ -21,7 +22,7 @@ mixin RouteControlsBasedCode { gatewayId: device.uuid!, ); case 'DL': - return DoorLockView(device: device); + return DoorLockControls(device: device); case 'WPS': return WallSensorControls(device: device); case 'CPS': @@ -29,11 +30,43 @@ mixin RouteControlsBasedCode { device: device, ); case 'CUR': - return CurtainStatusView( + return CurtainStatusControls( deviceId: device.uuid!, ); case 'AC': - return AcDeviceControl(device: device); + return AcDeviceControls(device: device); + default: + return const SizedBox(); + } + } + + Widget routeBatchControlsWidgets({required List devices}) { + switch (devices.first.productType) { + case '3G': + return LivingRoomBatchControls( + deviceIds: devices + .where((e) => e.productType == '3G') + .map((e) => e.uuid!) + .toList(), + ); + // case 'GW': + // return GateWayControls( + // gatewayId: device.first.uuid!, + // ); + // case 'DL': + // return DoorLockControls(device: device.first); + // case 'WPS': + // return WallSensorControls(device: device.first); + // case 'CPS': + // return CeilingSensorControls( + // device: device.first, + // ); + // case 'CUR': + // return CurtainStatusControls( + // deviceId: device.first.uuid!, + // ); + // case 'AC': + // return AcDeviceControls(device: device.first); default: return const SizedBox(); } diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index 13d3dd2e..a814a8b1 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -1,3 +1,4 @@ + import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; @@ -154,4 +155,65 @@ class AllDevicesModel { data['battery'] = batteryLevel; return data; } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is AllDevicesModel && + other.room == room && + other.unit == unit && + other.productUuid == productUuid && + other.productType == productType && + other.permissionType == permissionType && + other.activeTime == activeTime && + other.category == category && + other.categoryName == categoryName && + other.createTime == createTime && + other.gatewayId == gatewayId && + other.icon == icon && + other.ip == ip && + other.lat == lat && + other.localKey == localKey && + other.lon == lon && + other.model == model && + other.name == name && + other.nodeId == nodeId && + other.online == online && + other.ownerId == ownerId && + other.sub == sub && + other.timeZone == timeZone && + other.updateTime == updateTime && + other.uuid == uuid && + other.batteryLevel == batteryLevel; + } + + @override + int get hashCode { + return room.hashCode ^ + unit.hashCode ^ + productUuid.hashCode ^ + productType.hashCode ^ + permissionType.hashCode ^ + activeTime.hashCode ^ + category.hashCode ^ + categoryName.hashCode ^ + createTime.hashCode ^ + gatewayId.hashCode ^ + icon.hashCode ^ + ip.hashCode ^ + lat.hashCode ^ + localKey.hashCode ^ + lon.hashCode ^ + model.hashCode ^ + name.hashCode ^ + nodeId.hashCode ^ + online.hashCode ^ + ownerId.hashCode ^ + sub.hashCode ^ + timeZone.hashCode ^ + updateTime.hashCode ^ + uuid.hashCode ^ + batteryLevel.hashCode; + } } diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index ab2f07fc..936234cf 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/custom_table.dart'; @@ -27,6 +28,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { int offlineCount = 0; int lowBatteryCount = 0; bool isControlButtonEnabled = false; + List selectedDevices = []; if (state is DeviceManagementLoaded) { devicesToShow = state.devices; @@ -34,14 +36,18 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { onlineCount = state.onlineCount; offlineCount = state.offlineCount; lowBatteryCount = state.lowBatteryCount; - isControlButtonEnabled = state.selectedDevice != null; + isControlButtonEnabled = state.isControlButtonEnabled; + selectedDevices = state.selectedDevice ?? + context.read().selectedDevices; } else if (state is DeviceManagementFiltered) { devicesToShow = state.filteredDevices; selectedIndex = state.selectedIndex; onlineCount = state.onlineCount; offlineCount = state.offlineCount; lowBatteryCount = state.lowBatteryCount; - isControlButtonEnabled = state.selectedDevice != null; + isControlButtonEnabled = state.isControlButtonEnabled; + selectedDevices = state.selectedDevice ?? + context.read().selectedDevices; } else if (state is DeviceManagementInitial) { devicesToShow = []; selectedIndex = 0; @@ -55,8 +61,11 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { 'Low Battery ($lowBatteryCount)', ]; + final buttonLabel = + (selectedDevices.length > 1) ? 'Batch Control' : 'Control'; + return Column( - children: [ + children: [ Container( padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) @@ -69,7 +78,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { tabs: tabs, selectedIndex: selectedIndex, onTabChanged: (index) { - context.read() + context + .read() .add(SelectedFilterChanged(index)); }, ), @@ -84,19 +94,32 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { child: DefaultButton( onPressed: isControlButtonEnabled ? () { - final selectedDevice = context - .read() - .selectedDevices.first; - showDialog( - context: context, - builder: (context) => DeviceControlDialog( - device: selectedDevice), - ); + if (selectedDevices.length == 1) { + showDialog( + context: context, + builder: (context) => DeviceControlDialog( + device: selectedDevices.first, + ), + ); + } else if (selectedDevices.length > 1) { + final productTypes = selectedDevices + .map((device) => device.productType) + .toSet(); + if (productTypes.length == 1) { + showDialog( + context: context, + builder: (context) => + DeviceBatchControlDialog( + devices: selectedDevices, + ), + ); + } + } } : null, borderRadius: 9, child: Text( - 'Control', + buttonLabel, style: TextStyle( fontSize: 12, color: isControlButtonEnabled diff --git a/lib/pages/device_managment/curtain/view/curtain_status_view.dart b/lib/pages/device_managment/curtain/view/curtain_status_view.dart index c9af04fb..1de7fa83 100644 --- a/lib/pages/device_managment/curtain/view/curtain_status_view.dart +++ b/lib/pages/device_managment/curtain/view/curtain_status_view.dart @@ -6,10 +6,11 @@ import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.da import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class CurtainStatusView extends StatelessWidget with HelperResponsiveLayout { +class CurtainStatusControls extends StatelessWidget + with HelperResponsiveLayout { final String deviceId; - const CurtainStatusView({super.key, required this.deviceId}); + const CurtainStatusControls({super.key, required this.deviceId}); @override Widget build(BuildContext context) { diff --git a/lib/pages/device_managment/door_lock/view/door_lock_status_view.dart b/lib/pages/device_managment/door_lock/view/door_lock_status_view.dart index 536b0d97..6e24907a 100644 --- a/lib/pages/device_managment/door_lock/view/door_lock_status_view.dart +++ b/lib/pages/device_managment/door_lock/view/door_lock_status_view.dart @@ -7,10 +7,10 @@ import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_stat import 'package:syncrow_web/pages/device_managment/door_lock/models/door_lock_status_model.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/widget/door_button.dart'; -class DoorLockView extends StatelessWidget { +class DoorLockControls extends StatelessWidget { final AllDevicesModel device; - const DoorLockView({super.key, required this.device}); + const DoorLockControls({super.key, required this.device}); @override Widget build(BuildContext context) { diff --git a/lib/pages/device_managment/shared/device_batch_control_dialog.dart b/lib/pages/device_managment/shared/device_batch_control_dialog.dart new file mode 100644 index 00000000..18397253 --- /dev/null +++ b/lib/pages/device_managment/shared/device_batch_control_dialog.dart @@ -0,0 +1,81 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_controls_based_code.dart'; + +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DeviceBatchControlDialog extends StatelessWidget + with RouteControlsBasedCode { + final List devices; + + const DeviceBatchControlDialog({super.key, required this.devices}); + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: Colors.white, + insetPadding: const EdgeInsets.all(20), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: SizedBox( + width: 798, + height: context.screenHeight * 0.7, + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(), + Text( + devices.first.categoryName ?? 'Device Control', + 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(); + }, + ), + ), + ], + ), + const SizedBox(height: 20), + //// BUILD DEVICE CONTROLS + /// + //// ROUTE TO SPECIFIC CONTROL VIEW BASED ON DEVICE CATEGORY + routeBatchControlsWidgets(devices: devices), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart b/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart index de06248b..3bbe428c 100644 --- a/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart +++ b/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart @@ -18,21 +18,25 @@ class LivingRoomBloc extends Bloc { LivingRoomBloc({required this.deviceId}) : super(LivingRoomInitial()) { on(_onFetchDeviceStatus); on(_livingRoomControl); + on(_livingRoomBatchControl); } FutureOr _onFetchDeviceStatus( LivingRoomFetchDeviceStatus event, Emitter emit) async { emit(LivingRoomDeviceStatusLoading()); try { - final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = LivingRoomStatusModel.fromJson(event.deviceId, status.status); + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + LivingRoomStatusModel.fromJson(event.deviceId, status.status); emit(LivingRoomDeviceStatusLoaded(deviceStatus)); } catch (e) { emit(LivingRoomDeviceManagementError(e.toString())); } } - FutureOr _livingRoomControl(LivingRoomControl event, Emitter emit) async { + FutureOr _livingRoomControl( + LivingRoomControl event, Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -60,8 +64,8 @@ class LivingRoomBloc extends Bloc { } _timer = Timer(const Duration(seconds: 1), () async { try { - final response = - await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); + final response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); if (!response) { _revertValueAndEmit(deviceId, code, oldValue, emit); } @@ -71,8 +75,8 @@ class LivingRoomBloc extends Bloc { }); } - void _revertValueAndEmit( - String deviceId, String code, dynamic oldValue, Emitter emit) { + void _revertValueAndEmit(String deviceId, String code, dynamic oldValue, + Emitter emit) { _updateLocalValue(code, oldValue); emit(LivingRoomDeviceStatusLoaded(deviceStatus)); emit(const LivingRoomControlError('Failed to control the device.')); @@ -113,4 +117,20 @@ class LivingRoomBloc extends Bloc { return null; } } + + FutureOr _livingRoomBatchControl( + LivingRoomFetchBatchStatus event, Emitter emit) async { + emit(LivingRoomDeviceStatusLoading()); + try { + //TODO: get batch status from api + /// for now sending one id and getting the same value from fetch status + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + LivingRoomStatusModel.fromJson(event.deviceId, status.status); + emit(LivingRoomDeviceStatusLoaded(deviceStatus)); + } catch (e) { + emit(LivingRoomDeviceManagementError(e.toString())); + } + } } diff --git a/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart b/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart index f7b57cde..39cf84f1 100644 --- a/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart +++ b/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart @@ -16,6 +16,16 @@ class LivingRoomFetchDeviceStatus extends LivingRoomEvent { List get props => [deviceId]; } +//LivingRoomFetchBatchStatus +class LivingRoomFetchBatchStatus extends LivingRoomEvent { + final String deviceId; + + const LivingRoomFetchBatchStatus(this.deviceId); + + @override + List get props => [deviceId]; +} + class LivingRoomControl extends LivingRoomEvent { final String deviceId; final String code; diff --git a/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart new file mode 100644 index 00000000..7712cc02 --- /dev/null +++ b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart'; +import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class LivingRoomBatchControls extends StatelessWidget + with HelperResponsiveLayout { + const LivingRoomBatchControls({super.key, required this.deviceIds}); + + final List deviceIds; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => LivingRoomBloc(deviceId: deviceIds.first) + ..add(LivingRoomFetchBatchStatus(deviceIds.first)), + child: BlocBuilder( + builder: (context, state) { + if (state is LivingRoomDeviceStatusLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is LivingRoomDeviceStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is LivingRoomDeviceManagementError || + state is LivingRoomControlError) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls( + BuildContext context, LivingRoomStatusModel status) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return SizedBox( + child: 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, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceIds.first, + label: 'Wall Light', + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart b/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart index 0cf7ffa9..a0588d8d 100644 --- a/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart +++ b/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart @@ -5,23 +5,25 @@ import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/livi import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class LivingRoomDeviceControl extends StatelessWidget with HelperResponsiveLayout { +class LivingRoomDeviceControls extends StatelessWidget + with HelperResponsiveLayout { final String deviceId; - const LivingRoomDeviceControl({super.key, required this.deviceId}); + const LivingRoomDeviceControls({super.key, required this.deviceId}); @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => - LivingRoomBloc(deviceId: deviceId)..add(LivingRoomFetchDeviceStatus(deviceId)), + create: (context) => LivingRoomBloc(deviceId: deviceId) + ..add(LivingRoomFetchDeviceStatus(deviceId)), child: BlocBuilder( builder: (context, state) { if (state is LivingRoomDeviceStatusLoading) { return const Center(child: CircularProgressIndicator()); } else if (state is LivingRoomDeviceStatusLoaded) { return _buildStatusControls(context, state.status); - } else if (state is LivingRoomDeviceManagementError || state is LivingRoomControlError) { + } else if (state is LivingRoomDeviceManagementError || + state is LivingRoomControlError) { return const Center(child: Text('Error fetching status')); } else { return const Center(child: CircularProgressIndicator()); @@ -31,7 +33,8 @@ class LivingRoomDeviceControl extends StatelessWidget with HelperResponsiveLayou ); } - Widget _buildStatusControls(BuildContext context, LivingRoomStatusModel status) { + Widget _buildStatusControls( + BuildContext context, LivingRoomStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); diff --git a/pubspec.lock b/pubspec.lock index 8b9df6d6..2c9cb88c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -292,18 +292,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -340,18 +340,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" nested: dependency: transitive description: @@ -561,10 +561,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" typed_data: dependency: transitive description: @@ -609,10 +609,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" web: dependency: transitive description: From 28579e07786198c1bfb4659cb42fe262ffbdf5c7 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Thu, 12 Sep 2024 11:07:50 +0300 Subject: [PATCH 02/65] push door lock and living room batch design --- assets/icons/factory_reset.svg | 12 ++--- assets/icons/firmware.svg | 8 +++ lib/pages/common/custom_table.dart | 9 ++-- .../bloc/device_managment_bloc.dart | 28 +++++----- .../helper/route_controls_based_code.dart | 31 +++++++++-- .../widgets/device_managment_body.dart | 2 +- .../view/door_lock_batch_control_view.dart | 38 +++++++++++++ ..._view.dart => door_lock_control_view.dart} | 0 .../shared/batch_control/factory_reset.dart | 42 +++++++++++++++ .../shared/batch_control/firmware_update.dart | 43 +++++++++++++++ .../shared/device_batch_control_dialog.dart | 31 +++++++---- .../shared/device_control_dialog.dart | 3 +- .../view/living_room_batch_controls.dart | 7 +++ .../widgets/living_toggle_widget.dart | 54 ++++++++++--------- lib/utils/constants/assets.dart | 1 + 15 files changed, 241 insertions(+), 68 deletions(-) create mode 100644 assets/icons/firmware.svg create mode 100644 lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart rename lib/pages/device_managment/door_lock/view/{door_lock_status_view.dart => door_lock_control_view.dart} (100%) create mode 100644 lib/pages/device_managment/shared/batch_control/factory_reset.dart create mode 100644 lib/pages/device_managment/shared/batch_control/firmware_update.dart diff --git a/assets/icons/factory_reset.svg b/assets/icons/factory_reset.svg index 7a47f24b..b7297165 100644 --- a/assets/icons/factory_reset.svg +++ b/assets/icons/factory_reset.svg @@ -1,10 +1,4 @@ - - - - - - - - - + + + diff --git a/assets/icons/firmware.svg b/assets/icons/firmware.svg new file mode 100644 index 00000000..d636fd10 --- /dev/null +++ b/assets/icons/firmware.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index 317f7381..b6829ec2 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -86,8 +86,10 @@ class _DynamicTableState extends State { setState(() { _selectAll = value ?? false; _selected = List.filled(widget.data.length, _selectAll); - if (widget.selectAll != null) { - widget.selectAll!(_selectAll); + for (int i = 0; i < widget.data.length; i++) { + if (widget.onRowSelected != null) { + widget.onRowSelected!(i, _selectAll, widget.data[i]); + } } }); } @@ -185,8 +187,7 @@ class _DynamicTableState extends State { ), ), child: Checkbox( - value: widget.data.isNotEmpty && - _selected.every((element) => element == true), + value: _selectAll, onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null, diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart index 1919cb49..70c65fcc 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart @@ -115,17 +115,8 @@ class DeviceManagementBloc List clonedSelectedDevices = List.from(_selectedDevices); - bool isControlButtonEnabled = false; - - if (clonedSelectedDevices.length == 1) { - isControlButtonEnabled = true; - } else if (clonedSelectedDevices.length > 1) { - - final productTypes = - clonedSelectedDevices.map((device) => device.productType).toSet(); - isControlButtonEnabled = productTypes.length == - 1; - } + bool isControlButtonEnabled = + _checkIfControlButtonEnabled(clonedSelectedDevices); if (state is DeviceManagementLoaded) { emit(DeviceManagementLoaded( @@ -136,7 +127,7 @@ class DeviceManagementBloc lowBatteryCount: _lowBatteryCount, selectedDevice: clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null, - isControlButtonEnabled: isControlButtonEnabled, + isControlButtonEnabled: isControlButtonEnabled, )); } else if (state is DeviceManagementFiltered) { emit(DeviceManagementFiltered( @@ -147,11 +138,22 @@ class DeviceManagementBloc lowBatteryCount: _lowBatteryCount, selectedDevice: clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null, - isControlButtonEnabled: isControlButtonEnabled, + isControlButtonEnabled: isControlButtonEnabled, )); } } + bool _checkIfControlButtonEnabled(List selectedDevices) { + if (selectedDevices.length > 1) { + final productTypes = + selectedDevices.map((device) => device.productType).toSet(); + return productTypes.length == 1; + } else if (selectedDevices.length == 1) { + return true; + } + return false; + } + void _calculateDeviceCounts() { _onlineCount = _devices.where((device) => device.online == true).length; _offlineCount = _devices.where((device) => device.online == false).length; diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 5df77b2b..ffadb6f8 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -4,7 +4,8 @@ import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_control.dar import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart'; import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; -import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_status_view.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_device_control.dart'; @@ -40,12 +41,30 @@ mixin RouteControlsBasedCode { } } + /* + 3G: 2 occurrences + 1G: 1 occurrence + 2G: 1 occurrence + GW: 2 occurrences + DL: 2 occurrences + WPS: 2 occurrences + CPS: 2 occurrences + AC: 3 occurrences + CUR: 1 occurrence + + + */ + Widget routeBatchControlsWidgets({required List devices}) { switch (devices.first.productType) { case '3G': + case '1G': + case '2G': return LivingRoomBatchControls( deviceIds: devices - .where((e) => e.productType == '3G') + .where((e) => (e.productType == '3G' || + e.productType == '1G' || + e.productType == '2G')) .map((e) => e.uuid!) .toList(), ); @@ -53,8 +72,12 @@ mixin RouteControlsBasedCode { // return GateWayControls( // gatewayId: device.first.uuid!, // ); - // case 'DL': - // return DoorLockControls(device: device.first); + case 'DL': + return DoorLockBatchControlView( + devicesIds: devices + .where((e) => (e.productType == 'DL')) + .map((e) => e.uuid!) + .toList()); // case 'WPS': // return WallSensorControls(device: device.first); // case 'CPS': diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index 936234cf..d2f5f1de 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -139,7 +139,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { ? const EdgeInsets.all(30) : const EdgeInsets.all(15), child: DynamicTable( - withSelectAll: false, + withSelectAll: true, cellDecoration: containerDecoration, onRowSelected: (index, isSelected, row) { final selectedDevice = devicesToShow[index]; diff --git a/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart b/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart new file mode 100644 index 00000000..9c6190e0 --- /dev/null +++ b/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class DoorLockBatchControlView extends StatelessWidget + with HelperResponsiveLayout { + const DoorLockBatchControlView({super.key, required this.devicesIds}); + + final List devicesIds; + + @override + Widget build(BuildContext context) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return SizedBox( + child: GridView( + padding: const EdgeInsets.symmetric(horizontal: 50), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge || isExtraLarge || isMedium ? 2 : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + FirmwareUpdateWidget( + deviceId: devicesIds.first, + version: 12, + ), + FactoryResetWidget(deviceId: devicesIds.first), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/door_lock/view/door_lock_status_view.dart b/lib/pages/device_managment/door_lock/view/door_lock_control_view.dart similarity index 100% rename from lib/pages/device_managment/door_lock/view/door_lock_status_view.dart rename to lib/pages/device_managment/door_lock/view/door_lock_control_view.dart diff --git a/lib/pages/device_managment/shared/batch_control/factory_reset.dart b/lib/pages/device_managment/shared/batch_control/factory_reset.dart new file mode 100644 index 00000000..98f8f043 --- /dev/null +++ b/lib/pages/device_managment/shared/batch_control/factory_reset.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.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 FactoryResetWidget extends StatelessWidget { + const FactoryResetWidget({super.key, required String deviceId}); + + @override + Widget build(BuildContext context) { + return DeviceControlsContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ClipOval( + child: Container( + color: ColorsManager.whiteColors, + height: 60, + width: 60, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: SvgPicture.asset( + Assets.factoryReset, + fit: BoxFit.cover, + ), + ), + )), + Text( + 'Factory Reset', + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/batch_control/firmware_update.dart b/lib/pages/device_managment/shared/batch_control/firmware_update.dart new file mode 100644 index 00000000..bb7c7516 --- /dev/null +++ b/lib/pages/device_managment/shared/batch_control/firmware_update.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.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 FirmwareUpdateWidget extends StatelessWidget { + const FirmwareUpdateWidget( + {super.key, required String deviceId, required int version}); + + @override + Widget build(BuildContext context) { + return DeviceControlsContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ClipOval( + child: Container( + color: ColorsManager.whiteColors, + height: 60, + width: 60, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: SvgPicture.asset( + Assets.firmware, + fit: BoxFit.cover, + ), + ), + )), + Text( + 'Firmware Update', + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/device_batch_control_dialog.dart b/lib/pages/device_managment/shared/device_batch_control_dialog.dart index 18397253..1c3f3111 100644 --- a/lib/pages/device_managment/shared/device_batch_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_batch_control_dialog.dart @@ -22,8 +22,8 @@ class DeviceBatchControlDialog extends StatelessWidget borderRadius: BorderRadius.circular(20), ), child: SizedBox( - width: 798, - height: context.screenHeight * 0.7, + width: devices.length < 3 ? 500 : 798, + // height: context.screenHeight * 0.7, child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(20.0), @@ -34,13 +34,25 @@ class DeviceBatchControlDialog extends StatelessWidget mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const SizedBox(), - Text( - devices.first.categoryName ?? 'Device Control', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 22, - color: ColorsManager.dialogBlueTitle, - ), + Column( + children: [ + Text( + devices.first.categoryName ?? 'Device Control', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox( + height: 8, + ), + Text( + "Batch Control", + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.dialogBlueTitle, + ), + ), + ], ), Container( width: 25, @@ -66,6 +78,7 @@ class DeviceBatchControlDialog extends StatelessWidget ), ], ), + const SizedBox(height: 20), //// BUILD DEVICE CONTROLS /// diff --git a/lib/pages/device_managment/shared/device_control_dialog.dart b/lib/pages/device_managment/shared/device_control_dialog.dart index cde54047..77f52f62 100644 --- a/lib/pages/device_managment/shared/device_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_control_dialog.dart @@ -1,7 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_controls_based_code.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; @@ -22,7 +21,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { ), child: SizedBox( width: 798, - height: context.screenHeight * 0.7, + // height: context.screenHeight * 0.7, child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(20.0), diff --git a/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart index 7712cc02..b07ecbff 100644 --- a/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart +++ b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; @@ -60,6 +62,11 @@ class LivingRoomBatchControls extends StatelessWidget deviceId: deviceIds.first, label: 'Wall Light', ), + FirmwareUpdateWidget( + deviceId: deviceIds.first, + version: 12, + ), + FactoryResetWidget(deviceId: deviceIds.first), ], ), ); diff --git a/lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart b/lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart index abc75e66..62306a92 100644 --- a/lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart +++ b/lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart @@ -5,6 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.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 ToggleWidget extends StatelessWidget { final bool value; @@ -29,12 +30,13 @@ class ToggleWidget extends StatelessWidget { border: Border.all(color: ColorsManager.boxDivider), ), padding: const EdgeInsets.all(16), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.center, + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, children: [ ClipOval( child: Container( @@ -46,31 +48,31 @@ class ToggleWidget extends StatelessWidget { fit: BoxFit.cover, ), )), - SizedBox( - height: 20, - width: 35, - child: CupertinoSwitch( - value: value, - activeColor: ColorsManager.dialogBlueTitle, - onChanged: (newValue) { - context.read().add( - LivingRoomControl( - deviceId: deviceId, - code: code, - value: newValue, - ), - ); - }, + Text( + label, + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, ), ), ], ), - const Spacer(), - Text( - label, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14, + Container( + height: 20, + width: 35, + padding: const EdgeInsets.only(right: 16, top: 10), + child: CupertinoSwitch( + value: value, + activeColor: ColorsManager.dialogBlueTitle, + onChanged: (newValue) { + context.read().add( + LivingRoomControl( + deviceId: deviceId, + code: code, + value: newValue, + ), + ); + }, ), ), ], diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 5d2f5f2d..53fbb476 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -126,4 +126,5 @@ class Assets { static const String grid = "assets/images/grid.svg"; static const String curtainIcon = "assets/images/curtain.svg"; static const String unlock = 'assets/icons/unlock_ic.svg'; + static const String firmware = 'assets/icons/firmware.svg'; } From 3171916fafe328d79c873c9eb8651eb28209c413 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Thu, 12 Sep 2024 11:21:51 +0300 Subject: [PATCH 03/65] curtain batch control design --- .../helper/route_controls_based_code.dart | 12 ++-- .../curtain/bloc/curtain_bloc.dart | 16 +++++ .../curtain/bloc/curtain_event.dart | 11 ++- .../view/curtain_batch_status_view.dart | 68 +++++++++++++++++++ 4 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index ffadb6f8..9bd3db98 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_control.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_status_view.dart'; import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart'; @@ -84,10 +85,13 @@ mixin RouteControlsBasedCode { // return CeilingSensorControls( // device: device.first, // ); - // case 'CUR': - // return CurtainStatusControls( - // deviceId: device.first.uuid!, - // ); + case 'CUR': + return CurtainBatchStatusView( + devicesIds: devices + .where((e) => (e.productType == 'CUR')) + .map((e) => e.uuid!) + .toList(), + ); // case 'AC': // return AcDeviceControls(device: device.first); default: diff --git a/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart index 1b2f5864..484e2b80 100644 --- a/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart +++ b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart @@ -12,6 +12,7 @@ class CurtainBloc extends Bloc { CurtainBloc({required this.deviceId}) : super(CurtainInitial()) { on(_onFetchDeviceStatus); + on(_onFetchBatchStatus); on(_onCurtainControl); } @@ -88,4 +89,19 @@ class CurtainBloc extends Bloc { bool _checkStatus(String command) { return command.toLowerCase() == 'open'; } + + FutureOr _onFetchBatchStatus( + CurtainFetchBatchStatus event, Emitter emit) async { + emit(CurtainStatusLoading()); + try { + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + + deviceStatus = _checkStatus(status.status[0].value); + + emit(CurtainStatusLoaded(deviceStatus)); + } catch (e) { + emit(CurtainError(e.toString())); + } + } } diff --git a/lib/pages/device_managment/curtain/bloc/curtain_event.dart b/lib/pages/device_managment/curtain/bloc/curtain_event.dart index 23bb2e45..98670868 100644 --- a/lib/pages/device_managment/curtain/bloc/curtain_event.dart +++ b/lib/pages/device_managment/curtain/bloc/curtain_event.dart @@ -1,7 +1,5 @@ - import 'package:equatable/equatable.dart'; - sealed class CurtainEvent extends Equatable { const CurtainEvent(); @@ -18,6 +16,15 @@ class CurtainFetchDeviceStatus extends CurtainEvent { List get props => [deviceId]; } +class CurtainFetchBatchStatus extends CurtainEvent { + final String deviceId; + + const CurtainFetchBatchStatus(this.deviceId); + + @override + List get props => [deviceId]; +} + class CurtainControl extends CurtainEvent { final String deviceId; final String code; diff --git a/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart b/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart new file mode 100644 index 00000000..056e1d33 --- /dev/null +++ b/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/curtain_toggle.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart'; +import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class CurtainBatchStatusView extends StatelessWidget + with HelperResponsiveLayout { + const CurtainBatchStatusView({super.key, required this.devicesIds}); + + final List devicesIds; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => CurtainBloc(deviceId: devicesIds.first) + ..add(CurtainFetchDeviceStatus(devicesIds.first)), + child: BlocBuilder( + builder: (context, state) { + if (state is CurtainStatusLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is CurtainStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is CurtainError || state is CurtainControlError) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls(BuildContext context, bool 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, + ), + children: [ + CurtainToggle( + value: status, + code: 'control', + deviceId: devicesIds.first, + label: 'Curtains', + ), + FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5), + FactoryResetWidget(deviceId: devicesIds.first), + ], + ); + } +} From 222331f107db3d9c27facd1208a83ed1e62615b0 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Thu, 12 Sep 2024 11:49:57 +0300 Subject: [PATCH 04/65] ac batch control design --- .../device_managment/ac/bloc/ac_bloc.dart | 14 +++ .../device_managment/ac/bloc/ac_event.dart | 9 ++ .../ac/view/ac_device_batch_control.dart | 88 +++++++++++++++++++ .../helper/route_controls_based_code.dart | 8 +- 4 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 lib/pages/device_managment/ac/view/ac_device_batch_control.dart diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart index 5d147439..ab9cc265 100644 --- a/lib/pages/device_managment/ac/bloc/ac_bloc.dart +++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart @@ -15,6 +15,7 @@ class AcBloc extends Bloc { AcBloc({required this.deviceId}) : super(AcsInitialState()) { on(_onFetchAcStatus); + on(_onFetchAcBatchStatus); on(_onAcControl); } @@ -133,4 +134,17 @@ class AcBloc extends Bloc { return null; } } + + FutureOr _onFetchAcBatchStatus( + AcFetchBatchStatus event, Emitter emit) async { + emit(AcsLoadingState()); + try { + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); + emit(ACStatusLoaded(deviceStatus)); + } catch (e) { + emit(AcsFailedState(error: e.toString())); + } + } } diff --git a/lib/pages/device_managment/ac/bloc/ac_event.dart b/lib/pages/device_managment/ac/bloc/ac_event.dart index 400c8136..ab743b47 100644 --- a/lib/pages/device_managment/ac/bloc/ac_event.dart +++ b/lib/pages/device_managment/ac/bloc/ac_event.dart @@ -16,6 +16,15 @@ class AcFetchDeviceStatus extends AcsEvent { List get props => [deviceId]; } +class AcFetchBatchStatus extends AcsEvent { + final String deviceId; + + const AcFetchBatchStatus(this.deviceId); + + @override + List get props => [deviceId]; +} + class AcControl extends AcsEvent { final String deviceId; final String code; diff --git a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart new file mode 100644 index 00000000..3dcd72b1 --- /dev/null +++ b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart'; +import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_mode.dart'; +import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_toggle.dart'; +import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_temp.dart'; +import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class AcDeviceBatchControl extends StatelessWidget with HelperResponsiveLayout { + const AcDeviceBatchControl({super.key, required this.devicesIds}); + + final List devicesIds; + + @override + Widget build(BuildContext context) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return BlocProvider( + create: (context) => AcBloc(deviceId: devicesIds.first) + ..add(AcFetchBatchStatus(devicesIds.first)), + child: BlocBuilder( + builder: (context, state) { + if (state is ACStatusLoaded) { + 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, + ), + children: [ + AcToggle( + value: state.status.acSwitch, + code: 'switch', + deviceId: devicesIds.first, + ), + CurrentTemp( + currentTemp: state.status.currentTemp, + tempSet: state.status.tempSet, + code: 'temp_set', + deviceId: devicesIds.first, + ), + AcMode( + value: state.status.acMode, + code: 'mode', + deviceId: devicesIds.first, + ), + FanSpeedControl( + value: state.status.acFanSpeed, + code: 'level', + deviceId: devicesIds.first, + ), + AcToggle( + value: state.status.childLock, + code: 'child_lock', + deviceId: devicesIds.first, + description: 'Child Lock', + icon: + state.status.childLock ? Assets.childLock : Assets.unlock, + ), + FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5), + FactoryResetWidget(deviceId: devicesIds.first), + ], + ); + } else if (state is AcsLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else { + return const Center(child: Text('Error fetching status')); + } + }, + ), + ); + } +} diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 9bd3db98..c8518754 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_control.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart'; @@ -92,8 +93,11 @@ mixin RouteControlsBasedCode { .map((e) => e.uuid!) .toList(), ); - // case 'AC': - // return AcDeviceControls(device: device.first); + case 'AC': + return AcDeviceBatchControl(devicesIds: devices + .where((e) => (e.productType == 'AC')) + .map((e) => e.uuid!) + .toList()); default: return const SizedBox(); } From b4932c644e842320a7d16075145e09f7e87ba7d7 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Thu, 12 Sep 2024 13:41:13 +0300 Subject: [PATCH 05/65] push wall sensor --- .../ac/view/ac_device_batch_control.dart | 2 +- .../helper/route_controls_based_code.dart | 30 +++-- .../view/curtain_batch_status_view.dart | 2 +- .../view/door_lock_batch_control_view.dart | 2 +- .../gateway/view/gateway_batch_control.dart | 48 +++++++ .../shared/device_batch_control_dialog.dart | 2 +- .../wall_sensor/bloc/bloc.dart | 16 ++- .../wall_sensor/bloc/event.dart | 4 + .../view/wall_sensor_batch_control.dart | 121 ++++++++++++++++++ 9 files changed, 212 insertions(+), 15 deletions(-) create mode 100644 lib/pages/device_managment/gateway/view/gateway_batch_control.dart create mode 100644 lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart diff --git a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart index 3dcd72b1..c6597e99 100644 --- a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart @@ -29,7 +29,7 @@ class AcDeviceBatchControl extends StatelessWidget with HelperResponsiveLayout { builder: (context, state) { if (state is ACStatusLoaded) { return GridView( - padding: const EdgeInsets.symmetric(horizontal: 50), + padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index c8518754..06ef58d5 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -8,9 +8,11 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_device_control.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart'; mixin RouteControlsBasedCode { @@ -70,18 +72,25 @@ mixin RouteControlsBasedCode { .map((e) => e.uuid!) .toList(), ); - // case 'GW': - // return GateWayControls( - // gatewayId: device.first.uuid!, - // ); + case 'GW': + return GatewayBatchControl( + gatewayIds: devices + .where((e) => (e.productType == 'GW')) + .map((e) => e.uuid!) + .toList(), + ); case 'DL': return DoorLockBatchControlView( devicesIds: devices .where((e) => (e.productType == 'DL')) .map((e) => e.uuid!) .toList()); - // case 'WPS': - // return WallSensorControls(device: device.first); + case 'WPS': + return WallSensorBatchControl( + devicesIds: devices + .where((e) => (e.productType == 'WPS')) + .map((e) => e.uuid!) + .toList()); // case 'CPS': // return CeilingSensorControls( // device: device.first, @@ -94,10 +103,11 @@ mixin RouteControlsBasedCode { .toList(), ); case 'AC': - return AcDeviceBatchControl(devicesIds: devices - .where((e) => (e.productType == 'AC')) - .map((e) => e.uuid!) - .toList()); + return AcDeviceBatchControl( + devicesIds: devices + .where((e) => (e.productType == 'AC')) + .map((e) => e.uuid!) + .toList()); default: return const SizedBox(); } diff --git a/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart b/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart index 056e1d33..27a71e89 100644 --- a/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart +++ b/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart @@ -40,7 +40,7 @@ class CurtainBatchStatusView extends StatelessWidget final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); return GridView( - padding: const EdgeInsets.symmetric(horizontal: 50), + padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( diff --git a/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart b/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart index 9c6190e0..e4875f12 100644 --- a/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart +++ b/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart @@ -16,7 +16,7 @@ class DoorLockBatchControlView extends StatelessWidget final isMedium = isMediumScreenSize(context); return SizedBox( child: GridView( - padding: const EdgeInsets.symmetric(horizontal: 50), + padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( diff --git a/lib/pages/device_managment/gateway/view/gateway_batch_control.dart b/lib/pages/device_managment/gateway/view/gateway_batch_control.dart new file mode 100644 index 00000000..27e14998 --- /dev/null +++ b/lib/pages/device_managment/gateway/view/gateway_batch_control.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/gateway/bloc/gate_way_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class GatewayBatchControl extends StatelessWidget with HelperResponsiveLayout { + const GatewayBatchControl({super.key, required this.gatewayIds}); + + final List gatewayIds; + + @override + Widget build(BuildContext context) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + + return BlocProvider( + create: (context) => GateWayBloc()..add(GatWayById(gatewayIds.first)), + child: BlocBuilder( + builder: (context, state) { + if (state is GatewayLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is UpdateGatewayState) { + return GridView( + padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge || isExtraLarge || isMedium ? 2 : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + FirmwareUpdateWidget(deviceId: gatewayIds.first, version: 2), + FactoryResetWidget(deviceId: gatewayIds.first), + ], + ); + } else { + return const Center(child: Text('Error fetching status')); + } + }, + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/device_batch_control_dialog.dart b/lib/pages/device_managment/shared/device_batch_control_dialog.dart index 1c3f3111..8b87a964 100644 --- a/lib/pages/device_managment/shared/device_batch_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_batch_control_dialog.dart @@ -22,7 +22,7 @@ class DeviceBatchControlDialog extends StatelessWidget borderRadius: BorderRadius.circular(20), ), child: SizedBox( - width: devices.length < 3 ? 500 : 798, + width: devices.length < 2 ? 500 : 800, // height: context.screenHeight * 0.7, child: SingleChildScrollView( child: Padding( diff --git a/lib/pages/device_managment/wall_sensor/bloc/bloc.dart b/lib/pages/device_managment/wall_sensor/bloc/bloc.dart index bda4d968..86e1d504 100644 --- a/lib/pages/device_managment/wall_sensor/bloc/bloc.dart +++ b/lib/pages/device_managment/wall_sensor/bloc/bloc.dart @@ -13,6 +13,7 @@ class WallSensorBloc extends Bloc { WallSensorBloc({required this.deviceId}) : super(WallSensorInitialState()) { on(_fetchWallSensorStatus); + on(_fetchWallSensorBatchControl); on(_changeValue); on(_getDeviceReports); on(_showDescription); @@ -99,7 +100,7 @@ class WallSensorBloc extends Bloc { try { await DevicesManagementApi.getDeviceReports(deviceId, event.code) .then((value) { - emit(DeviceReportsState(deviceReport: value, code:event.code)); + emit(DeviceReportsState(deviceReport: value, code: event.code)); }); } catch (e) { emit(DeviceReportsFailedState(error: e.toString())); @@ -116,4 +117,17 @@ class WallSensorBloc extends Bloc { BackToGridViewEvent event, Emitter emit) { emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); } + + FutureOr _fetchWallSensorBatchControl( + WallSensorBatchControlEvent event, Emitter emit) async { + emit(WallSensorLoadingInitialState()); + try { + var response = await DevicesManagementApi().getDeviceStatus(deviceId); + deviceStatus = WallSensorModel.fromJson(response.status); + emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); + } catch (e) { + emit(WallSensorFailedState(error: e.toString())); + return; + } + } } diff --git a/lib/pages/device_managment/wall_sensor/bloc/event.dart b/lib/pages/device_managment/wall_sensor/bloc/event.dart index d3c20ba7..c40d5e3e 100644 --- a/lib/pages/device_managment/wall_sensor/bloc/event.dart +++ b/lib/pages/device_managment/wall_sensor/bloc/event.dart @@ -18,6 +18,10 @@ class WallSensorChangeValueEvent extends WallSensorEvent { List get props => [value, code]; } +class WallSensorBatchControlEvent extends WallSensorEvent { + const WallSensorBatchControlEvent(); +} + class GetDeviceReportsEvent extends WallSensorEvent { final String deviceUuid; final String code; diff --git a/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart new file mode 100644 index 00000000..88d1270a --- /dev/null +++ b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/bloc.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/event.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/state.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class WallSensorBatchControl extends StatelessWidget + with HelperResponsiveLayout { + const WallSensorBatchControl({super.key, required this.devicesIds}); + + final List devicesIds; + + @override + Widget build(BuildContext context) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return BlocProvider( + create: (context) => WallSensorBloc(deviceId: devicesIds.first) + ..add(const WallSensorBatchControlEvent()), + child: BlocBuilder( + builder: (context, state) { + if (state is WallSensorLoadingInitialState || + state is DeviceReportsLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is WallSensorUpdateState) { + return _buildGridView(context, state.wallSensorModel, isExtraLarge, + isLarge, isMedium); + } else if (state is DeviceReportsFailedState) { + final model = context.read().deviceStatus; + return _buildGridView( + context, model, isExtraLarge, isLarge, isMedium); + } + return const Center(child: Text('Error fetching status')); + }, + ), + ); + } + + Widget _buildGridView(BuildContext context, WallSensorModel model, + bool isExtraLarge, bool isLarge, bool isMedium) { + return GridView( + padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge || isExtraLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + PresenceUpdateData( + value: model.motionSensitivity.toDouble(), + title: 'Motion Detection Sensitivity:', + minValue: 1, + maxValue: 5, + steps: 1, + action: (int value) { + context.read().add( + WallSensorChangeValueEvent( + code: 'motion_sensitivity_value', + value: value, + ), + ); + }, + ), + PresenceUpdateData( + value: model.motionlessSensitivity.toDouble(), + title: 'Motionless Detection Sensitivity:', + minValue: 1, + maxValue: 5, + steps: 1, + action: (int value) => context.read().add( + WallSensorChangeValueEvent( + code: 'motionless_sensitivity', + value: value, + ), + ), + ), + PresenceUpdateData( + value: model.noBodyTime.toDouble(), + title: 'Nobody Time:', + minValue: 10, + maxValue: 10000, + steps: 1, + description: 'hr', + action: (int value) => + context.read().add(WallSensorChangeValueEvent( + code: 'no_one_time', + value: value, + ))), + PresenceUpdateData( + value: model.farDetection.toDouble(), + title: 'Far Detection:', + minValue: 75, + maxValue: 600, + steps: 75, + description: 'cm', + action: (int value) => context.read().add( + WallSensorChangeValueEvent( + code: 'far_detection', + value: value, + ), + ), + ), + FirmwareUpdateWidget(deviceId: devicesIds.first, version: 2), + FactoryResetWidget(deviceId: devicesIds.first), + ], + ); + } +} From ebd92c10118ffb716539fdaedd0a15a48e9e204e Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Thu, 12 Sep 2024 13:49:40 +0300 Subject: [PATCH 06/65] push ceiling sensor batch --- .../helper/route_controls_based_code.dart | 12 +- .../ceiling_sensor/bloc/bloc.dart | 14 +++ .../ceiling_sensor/bloc/event.dart | 2 + .../view/ceiling_sensor_batch_control.dart | 119 ++++++++++++++++++ 4 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 06ef58d5..9acce9f7 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/ac_device_control.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart'; import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_status_view.dart'; import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; @@ -91,10 +92,13 @@ mixin RouteControlsBasedCode { .where((e) => (e.productType == 'WPS')) .map((e) => e.uuid!) .toList()); - // case 'CPS': - // return CeilingSensorControls( - // device: device.first, - // ); + case 'CPS': + return CeilingSensorBatchControl( + devicesIds: devices + .where((e) => (e.productType == 'CPS')) + .map((e) => e.uuid!) + .toList(), + ); case 'CUR': return CurtainBatchStatusView( devicesIds: devices diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart b/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart index 570c1e9c..055c21b1 100644 --- a/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart +++ b/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart @@ -14,6 +14,7 @@ class CeilingSensorBloc extends Bloc { CeilingSensorBloc({required this.deviceId}) : super(CeilingInitialState()) { on(_fetchCeilingSensorStatus); + on(_fetchCeilingSensorBatchControl); on(_changeValue); on(_getDeviceReports); on(_showDescription); @@ -128,4 +129,17 @@ class CeilingSensorBloc extends Bloc { BackToCeilingGridViewEvent event, Emitter emit) { emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); } + + FutureOr _fetchCeilingSensorBatchControl( + CeilingBatchControlEvent event, Emitter emit) async { + emit(CeilingLoadingInitialState()); + try { + var response = await DevicesManagementApi().getDeviceStatus(deviceId); + deviceStatus = CeilingSensorModel.fromJson(response.status); + emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); + } catch (e) { + emit(CeilingFailedState(error: e.toString())); + return; + } + } } diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/event.dart b/lib/pages/device_managment/ceiling_sensor/bloc/event.dart index 31c5ab56..d445578e 100644 --- a/lib/pages/device_managment/ceiling_sensor/bloc/event.dart +++ b/lib/pages/device_managment/ceiling_sensor/bloc/event.dart @@ -9,6 +9,8 @@ abstract class CeilingSensorEvent extends Equatable { class CeilingInitialEvent extends CeilingSensorEvent {} +class CeilingBatchControlEvent extends CeilingSensorEvent {} + class CeilingChangeValueEvent extends CeilingSensorEvent { final dynamic value; final String code; diff --git a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart new file mode 100644 index 00000000..ca52c148 --- /dev/null +++ b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/event.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/state.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class CeilingSensorBatchControl extends StatelessWidget + with HelperResponsiveLayout { + const CeilingSensorBatchControl({super.key, required this.devicesIds}); + + final List devicesIds; + + @override + Widget build(BuildContext context) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return BlocProvider( + create: (context) => CeilingSensorBloc(deviceId: devicesIds.first) + ..add(CeilingBatchControlEvent()), + child: BlocBuilder( + builder: (context, state) { + if (state is CeilingLoadingInitialState || + state is CeilingReportsLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is CeilingUpdateState) { + return _buildGridView(context, state.ceilingSensorModel, + isExtraLarge, isLarge, isMedium); + } else if (state is CeilingReportsFailedState) { + final model = context.read().deviceStatus; + return _buildGridView( + context, model, isExtraLarge, isLarge, isMedium); + } + return const Center(child: Text('Error fetching status')); + }, + ), + ); + } + + Widget _buildGridView(BuildContext context, CeilingSensorModel model, + bool isExtraLarge, bool isLarge, bool isMedium) { + 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, + ), + children: [ + PresenceSpaceType( + description: 'Space Type', + value: model.spaceType, + action: (String value) => context.read().add( + CeilingChangeValueEvent( + code: 'scene', + value: value, + ), + ), + ), + PresenceUpdateData( + value: model.sensitivity.toDouble(), + title: 'Sensitivity:', + minValue: 1, + maxValue: 5, + steps: 1, + action: (int value) { + context.read().add( + CeilingChangeValueEvent( + code: 'sensitivity', + value: value, + ), + ); + }, + ), + PresenceUpdateData( + value: model.maxDistance.toDouble(), + title: 'Maximum Distance:', + minValue: 0, + maxValue: 500, + steps: 50, + description: 'm', + action: (int value) => context.read().add( + CeilingChangeValueEvent( + code: 'moving_max_dis', + value: value, + ), + ), + ), + PresenceNoBodyTime( + value: model.noBodyTime, + title: 'Nobody Time:', + description: '', + action: (String value) => context.read().add( + CeilingChangeValueEvent( + code: 'nobody_time', + value: value, + ), + ), + ), + FirmwareUpdateWidget(deviceId: devicesIds.first, version: 4), + FactoryResetWidget(deviceId: devicesIds.first), + ], + ); + } +} From 7c97d01ddbd1c3804d8dfa8652eb8afd107eea75 Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 12 Sep 2024 14:54:04 +0300 Subject: [PATCH 07/65] Authorizer search field & Date-Time Filters & Update Table View Columns --- .../access_management/bloc/access_bloc.dart | 131 +++++++++++------- .../access_management/bloc/access_event.dart | 2 + .../model/password_model.dart | 18 ++- .../view/access_management.dart | 37 +++-- .../auth/view/forget_password_web_page.dart | 18 +-- .../bloc/visitor_password_bloc.dart | 9 +- lib/services/access_mang_api.dart | 1 + 7 files changed, 136 insertions(+), 80 deletions(-) diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart index 94b0992f..4b7e499c 100644 --- a/lib/pages/access_management/bloc/access_bloc.dart +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -3,25 +3,27 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_event.dart'; import 'package:syncrow_web/pages/access_management/bloc/access_state.dart'; import 'package:syncrow_web/pages/access_management/model/password_model.dart'; +import 'package:syncrow_web/pages/common/hour_picker_dialog.dart'; import 'package:syncrow_web/services/access_mang_api.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; class AccessBloc extends Bloc { AccessBloc() : super((AccessInitial())) { on(_onFetchTableData); - // on(selectFilterTap); on(selectTime); on(_filterData); on(resetSearch); on(onTabChanged); } + String startTime = 'Start Date'; String endTime = 'End Date'; - int? effectiveTimeTimeStamp; int? expirationTimeTimeStamp; TextEditingController passwordName = TextEditingController(); + TextEditingController emailAuthorizer = TextEditingController(); List filteredData = []; List data = []; @@ -62,57 +64,61 @@ class AccessBloc extends Bloc { } } - Future selectTime(SelectTime event, Emitter emit) async { + + Future selectTime(SelectTime event, Emitter emit,) async { emit(AccessLoaded()); final DateTime? picked = await showDatePicker( context: event.context, initialDate: DateTime.now(), - firstDate: DateTime(2015, 8), - lastDate: DateTime(2101), + firstDate: DateTime.now().add(const Duration(days: -5095)), + lastDate: DateTime.now().add(const Duration(days: 2095)), ); if (picked != null) { - final selectedDateTime = DateTime( - picked.year, - picked.month, - picked.day, + final TimeOfDay? timePicked = await showHourPicker( + context: event.context, + initialTime: TimeOfDay.now(), ); - final selectedTimestamp = DateTime( - selectedDateTime.year, - selectedDateTime.month, - selectedDateTime.day, - selectedDateTime.hour, - selectedDateTime.minute, - ).millisecondsSinceEpoch ~/ - 1000; // Divide by 1000 to remove milliseconds - if (event.isStart) { - if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) { - CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.'); + + if (timePicked != null) { + final DateTime selectedDateTime = DateTime( + picked.year, + picked.month, + picked.day, + timePicked.hour, + timePicked.minute, + ); + final int selectedTimestamp = selectedDateTime.millisecondsSinceEpoch ~/ 1000; + if (event.isStart) { + if (expirationTimeTimeStamp != null && selectedTimestamp > expirationTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Effective Time cannot be later than Expiration Time.'); + } else { + startTime = selectedDateTime.toString().split('.').first; + effectiveTimeTimeStamp = selectedTimestamp; + } } else { - startTime = - selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds - effectiveTimeTimeStamp = selectedTimestamp; - } - } else { - if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { - CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.'); - } else { - endTime = selectedDateTime.toString().split('.').first; // Remove seconds and milliseconds - expirationTimeTimeStamp = selectedTimestamp; + if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { + CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.'); + } else { + endTime = selectedDateTime.toString().split('.').first; + expirationTimeTimeStamp = selectedTimestamp; + } } } } emit(ChangeTimeState()); + } + Future _filterData(FilterDataEvent event, Emitter emit) async { emit(AccessLoaded()); try { + print(event.emailAuthorizer?.toLowerCase()); // Convert search text to lower case for case-insensitive search final searchText = event.passwordName?.toLowerCase() ?? ''; - + final searchEmailText = event.emailAuthorizer?.toLowerCase() ?? ''; filteredData = data.where((item) { bool matchesCriteria = true; - // Convert timestamp to DateTime and extract date component DateTime effectiveDate = DateTime.fromMillisecondsSinceEpoch(int.parse(item.effectiveTime.toString()) * 1000) @@ -122,9 +128,8 @@ class AccessBloc extends Bloc { DateTime.fromMillisecondsSinceEpoch(int.parse(item.invalidTime.toString()) * 1000) .toUtc() .toLocal(); - DateTime effectiveDateOnly = - DateTime(effectiveDate.year, effectiveDate.month, effectiveDate.day); - DateTime invalidDateOnly = DateTime(invalidDate.year, invalidDate.month, invalidDate.day); + DateTime effectiveDateAndTime = DateTime(effectiveDate.year, effectiveDate.month, effectiveDate.day,effectiveDate.hour,effectiveDate.minute); + DateTime invalidDateAndTime = DateTime(invalidDate.year, invalidDate.month, invalidDate.day,invalidDate.hour,invalidDate.minute); // Filter by password name, making the search case-insensitive if (searchText.isNotEmpty) { @@ -133,36 +138,51 @@ class AccessBloc extends Bloc { matchesCriteria = false; } } - - // Filter by start date only - if (event.startTime != null && event.endTime == null) { - DateTime startDateOnly = - DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal(); - startDateOnly = DateTime(startDateOnly.year, startDateOnly.month, startDateOnly.day); - if (effectiveDateOnly.isBefore(startDateOnly)) { + if (searchEmailText.isNotEmpty) { + final bool matchesName = item.authorizerEmail.toString().toLowerCase().contains(searchEmailText); + if (!matchesName) { + matchesCriteria = false; + } + } + // Filter by start date only + if (event.startTime != null && event.endTime == null) { + DateTime startDateTime = + DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal(); + startDateTime = DateTime( + startDateTime.year, + startDateTime.month, + startDateTime.day, + startDateTime.hour, + startDateTime.minute); + if (effectiveDateAndTime.isBefore(startDateTime)) { matchesCriteria = false; } } - // Filter by end date only if (event.endTime != null && event.startTime == null) { - DateTime endDateOnly = + DateTime startDateTime = DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal(); - endDateOnly = DateTime(endDateOnly.year, endDateOnly.month, endDateOnly.day); - if (invalidDateOnly.isAfter(endDateOnly)) { + startDateTime = DateTime( + startDateTime.year, + startDateTime.month, + startDateTime.day, + startDateTime.hour, + startDateTime.minute + ); + if (invalidDateAndTime.isAfter(startDateTime)) { matchesCriteria = false; } } // Filter by both start date and end date if (event.startTime != null && event.endTime != null) { - DateTime startDateOnly = + DateTime startDateTime = DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal(); - DateTime endDateOnly = + DateTime endDateTime = DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal(); - startDateOnly = DateTime(startDateOnly.year, startDateOnly.month, startDateOnly.day); - endDateOnly = DateTime(endDateOnly.year, endDateOnly.month, endDateOnly.day); - if (effectiveDateOnly.isBefore(startDateOnly) || invalidDateOnly.isAfter(endDateOnly)) { + startDateTime = DateTime(startDateTime.year, startDateTime.month, startDateTime.day,startDateTime.hour,startDateTime.minute); + endDateTime = DateTime(endDateTime.year, endDateTime.month, endDateTime.day,endDateTime.hour,endDateTime.minute); + if (effectiveDateAndTime.isBefore(startDateTime) || invalidDateAndTime.isAfter(endDateTime)) { matchesCriteria = false; } } @@ -186,12 +206,12 @@ class AccessBloc extends Bloc { } - resetSearch(ResetSearch event, Emitter emit) async { emit(AccessLoaded()); startTime = 'Start Time'; endTime = 'End Time'; passwordName.clear(); + emailAuthorizer.clear(); selectedIndex = 0; effectiveTimeTimeStamp = null; expirationTimeTimeStamp = null; @@ -200,9 +220,11 @@ class AccessBloc extends Bloc { String timestampToDate(dynamic timestamp) { DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(int.parse(timestamp) * 1000); - return "${dateTime.year}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.day.toString().padLeft(2, '0')}"; + return "${dateTime.year}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.day.toString().padLeft(2, '0')} " + " ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}"; } + Future onTabChanged(TabChangedEvent event, Emitter emit) async { try { emit(AccessLoaded()); @@ -227,6 +249,7 @@ class AccessBloc extends Bloc { add(FilterDataEvent( selectedTabIndex: selectedIndex, passwordName: passwordName.text.toLowerCase(), + emailAuthorizer: emailAuthorizer.text.toLowerCase(), startTime: effectiveTimeTimeStamp, endTime: expirationTimeTimeStamp)); emit(TableLoaded(filteredData)); @@ -234,4 +257,6 @@ class AccessBloc extends Bloc { emit(FailedState(e.toString())); } } + } + diff --git a/lib/pages/access_management/bloc/access_event.dart b/lib/pages/access_management/bloc/access_event.dart index 1bd7dbd3..4ef71ea6 100644 --- a/lib/pages/access_management/bloc/access_event.dart +++ b/lib/pages/access_management/bloc/access_event.dart @@ -28,12 +28,14 @@ class SelectTime extends AccessEvent { class FilterDataEvent extends AccessEvent { final String? passwordName; + final String? emailAuthorizer; final int? startTime; final int? endTime; final int selectedTabIndex; // Add this field const FilterDataEvent({ this.passwordName, + this.emailAuthorizer, this.startTime, this.endTime, required this.selectedTabIndex, // Initialize this field diff --git a/lib/pages/access_management/model/password_model.dart b/lib/pages/access_management/model/password_model.dart index 50c03090..0ce4426a 100644 --- a/lib/pages/access_management/model/password_model.dart +++ b/lib/pages/access_management/model/password_model.dart @@ -6,10 +6,13 @@ class PasswordModel { final dynamic effectiveTime; final dynamic passwordCreated; final dynamic createdTime; - final dynamic passwordName; // New field + final dynamic passwordName; final AccessStatus passwordStatus; final AccessType passwordType; final dynamic deviceUuid; + final dynamic authorizerEmail; + final dynamic authorizerDate; + final dynamic deviceName; PasswordModel({ this.passwordId, @@ -17,10 +20,13 @@ class PasswordModel { this.effectiveTime, this.passwordCreated, this.createdTime, - this.passwordName, // New field + this.passwordName, required this.passwordStatus, required this.passwordType, this.deviceUuid, + this.authorizerEmail, + this.authorizerDate, + this.deviceName, }); factory PasswordModel.fromJson(Map json) { @@ -34,6 +40,9 @@ class PasswordModel { passwordStatus: AccessStatusExtension.fromString(json['passwordStatus']), passwordType: AccessTypeExtension.fromString(json['passwordType']), deviceUuid: json['deviceUuid'], + authorizerEmail: json['authorizerEmail'], + authorizerDate: json['authorizerDate'], + deviceName: json['deviceName'], ); } @@ -44,10 +53,13 @@ class PasswordModel { 'effectiveTime': effectiveTime, 'passwordCreated': passwordCreated, 'createdTime': createdTime, - 'passwodName': passwordName, // New field + 'passwordName': passwordName, // New field 'passwordStatus': passwordStatus, 'passwordType': passwordType, 'deviceUuid': deviceUuid, + 'authorizerEmail': authorizerEmail, + 'authorizerDate': authorizerDate, + 'deviceName': deviceName, }; } } diff --git a/lib/pages/access_management/view/access_management.dart b/lib/pages/access_management/view/access_management.dart index f66c8a99..b8c89453 100644 --- a/lib/pages/access_management/view/access_management.dart +++ b/lib/pages/access_management/view/access_management.dart @@ -24,6 +24,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { @override Widget build(BuildContext context) { + final isLargeScreen = isLargeScreenSize(context); final isSmallScreen = isSmallScreenSize(context); final isHalfMediumScreen = isHafMediumScreenSize(context); @@ -85,7 +86,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { _buildVisitorAdminPasswords(context, accessBloc), const SizedBox(height: 20), Expanded( - child: DynamicTable( + child: DynamicTable( tableName: 'AccessManagement', uuidIndex: 1, withSelectAll: true, @@ -96,7 +97,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { headers: const [ 'Name', 'Access Type', - 'Access Period', + 'Access Start', + 'Access End', 'Accessible Device', 'Authorizer', 'Authorization Date & Time', @@ -106,10 +108,11 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { return [ item.passwordName, item.passwordType.value, - ('${accessBloc.timestampToDate(item.effectiveTime)} - ${accessBloc.timestampToDate(item.invalidTime)}'), - item.deviceUuid.toString(), - '', - '', + accessBloc.timestampToDate(item.effectiveTime), + accessBloc.timestampToDate(item.invalidTime), + item.deviceName.toString(), + item.authorizerEmail.toString(), + accessBloc.timestampToDate(item.invalidTime), item.passwordStatus.value, ]; }).toList(), @@ -166,6 +169,8 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { } Row _buildNormalSearchWidgets(BuildContext context, AccessBloc accessBloc) { + TimeOfDay _selectedTime = TimeOfDay.now(); + return Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, @@ -182,6 +187,17 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { ), ), const SizedBox(width: 15), + SizedBox( + width: 250, + child: CustomWebTextField( + controller: accessBloc.emailAuthorizer, + height: 43, + isRequired: false, + textFieldName: 'Authorizer', + description: '', + ), + ), + const SizedBox(width: 15), SizedBox( child: DateTimeWebWidget( icon: Assets.calendarIcon, @@ -189,7 +205,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { title: 'Access Time', size: MediaQuery.of(context).size, endTime: () { - accessBloc.add(SelectTime(context: context, isStart: false)); + accessBloc.add(SelectTime(context: context, isStart: false)); }, startTime: () { accessBloc.add(SelectTime(context: context, isStart: true)); @@ -202,6 +218,7 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { SearchResetButtons( onSearch: () { accessBloc.add(FilterDataEvent( + emailAuthorizer:accessBloc.emailAuthorizer.text.toLowerCase() , selectedTabIndex: BlocProvider.of(context).selectedIndex, passwordName: accessBloc.passwordName.text.toLowerCase(), startTime: accessBloc.effectiveTimeTimeStamp, @@ -247,10 +264,12 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { SearchResetButtons( onSearch: () { accessBloc.add(FilterDataEvent( + emailAuthorizer:accessBloc.emailAuthorizer.text.toLowerCase() , selectedTabIndex: BlocProvider.of(context).selectedIndex, passwordName: accessBloc.passwordName.text.toLowerCase(), startTime: accessBloc.effectiveTimeTimeStamp, - endTime: accessBloc.expirationTimeTimeStamp)); + endTime: accessBloc.expirationTimeTimeStamp + )); }, onReset: () { accessBloc.add(ResetSearch()); @@ -259,4 +278,6 @@ class AccessManagementPage extends StatelessWidget with HelperResponsiveLayout { ], ); } + + } diff --git a/lib/pages/auth/view/forget_password_web_page.dart b/lib/pages/auth/view/forget_password_web_page.dart index d391776e..e89b9361 100644 --- a/lib/pages/auth/view/forget_password_web_page.dart +++ b/lib/pages/auth/view/forget_password_web_page.dart @@ -209,23 +209,17 @@ class ForgetPasswordWebPage extends StatelessWidget { decoration: textBoxDecoration()!.copyWith( hintText: 'Enter Code', - hintStyle: Theme.of(context) - .textTheme - .bodySmall! - .copyWith( - color: - ColorsManager.grayColor, - fontWeight: - FontWeight.w400), + hintStyle: Theme.of(context).textTheme + .bodySmall!.copyWith( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400), suffixIcon: SizedBox( width: 100, child: Center( child: InkWell( onTap: state is TimerState && - !state - .isButtonEnabled && - state.remainingTime != - 1 + !state.isButtonEnabled && + state.remainingTime != 1 ? null : () { if (forgetBloc.forgetEmailKey.currentState!.validate()||forgetBloc.forgetRegionKey.currentState!.validate()) { diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index 02f5ef0a..317a08a9 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -75,13 +75,12 @@ class VisitorPasswordBloc extends Bloc selectTimeVisitorPassword( SelectTimeVisitorPassword event, - Emitter emit, - ) async { + Emitter emit,) async { final DateTime? picked = await showDatePicker( context: event.context, initialDate: DateTime.now(), firstDate: DateTime(2015, 8), - lastDate: DateTime(3101), + lastDate: DateTime.now().add(const Duration(days: 5095)), ); if (picked != null) { @@ -352,13 +351,15 @@ class VisitorPasswordBloc extends Bloc jsonData = json; List passwordList = jsonData.map((jsonItem) { return PasswordModel.fromJson(jsonItem); From b513f31214b633b1e9615006a02264942bb69aee Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 15 Sep 2024 23:21:06 +0300 Subject: [PATCH 08/65] push 1gang and 2 gang, better naming --- .../ac/view/ac_device_batch_control.dart | 5 +- .../ac/view/ac_device_control.dart | 4 +- .../helper/route_controls_based_code.dart | 78 +++++++---- .../view/ceiling_sensor_batch_control.dart | 4 +- .../view/ceiling_sensor_controls.dart | 4 +- .../curtain/view/curtain_status_view.dart | 4 +- .../view/door_lock_control_view.dart | 4 +- .../gateway/view/gateway_batch_control.dart | 5 +- .../gateway/view/gateway_view.dart | 4 +- .../bloc/wall_light_switch_bloc.dart | 120 +++++++++++++++++ .../bloc/wall_light_switch_event.dart | 48 +++++++ .../bloc/wall_light_switch_state.dart | 56 ++++++++ .../models/wall_light_status_model.dart | 47 +++++++ .../view/wall_light_batch_control.dart | 77 +++++++++++ .../view/wall_light_device_control.dart | 75 +++++++++++ .../bloc/living_room_event.dart | 12 ++ .../view/living_room_batch_controls.dart | 5 +- .../view/living_room_device_control.dart | 90 +++++++------ .../widgets/living_toggle_widget.dart | 14 +- .../bloc/two_gang_switch_bloc.dart | 125 ++++++++++++++++++ .../bloc/two_gang_switch_event.dart | 48 +++++++ .../bloc/two_gang_switch_state.dart | 56 ++++++++ .../models/two_gang_status_model.dart | 65 +++++++++ .../view/wall_light_batch_control.dart | 83 ++++++++++++ .../view/wall_light_device_control.dart | 85 ++++++++++++ .../view/wall_sensor_batch_control.dart | 4 +- .../view/wall_sensor_conrtols.dart | 5 +- 27 files changed, 1028 insertions(+), 99 deletions(-) create mode 100644 lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart create mode 100644 lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart create mode 100644 lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart create mode 100644 lib/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart create mode 100644 lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart create mode 100644 lib/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart create mode 100644 lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart create mode 100644 lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart create mode 100644 lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart create mode 100644 lib/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart create mode 100644 lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart create mode 100644 lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart diff --git a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart index c6597e99..dc597090 100644 --- a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart @@ -12,8 +12,9 @@ import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class AcDeviceBatchControl extends StatelessWidget with HelperResponsiveLayout { - const AcDeviceBatchControl({super.key, required this.devicesIds}); +class AcDeviceBatchControlView extends StatelessWidget + with HelperResponsiveLayout { + const AcDeviceBatchControlView({super.key, required this.devicesIds}); final List devicesIds; diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index 123ffdfd..fdfa263c 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -11,8 +11,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class AcDeviceControls extends StatelessWidget with HelperResponsiveLayout { - const AcDeviceControls({super.key, required this.device}); +class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { + const AcDeviceControlsView({super.key, required this.device}); final AllDevicesModel device; diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 9acce9f7..1fb82348 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -11,70 +11,90 @@ import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batc import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_device_control.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart'; mixin RouteControlsBasedCode { Widget routeControlsWidgets({required AllDevicesModel device}) { switch (device.productType) { + case '1G': + return WallLightDeviceControl( + deviceId: device.uuid!, + ); + case '2G': + return TwoGangDeviceControlView( + deviceId: device.uuid!, + ); case '3G': - return LivingRoomDeviceControls( + return LivingRoomDeviceControlsView( deviceId: device.uuid!, ); case 'GW': - return GateWayControls( + return GateWayControlsView( gatewayId: device.uuid!, ); case 'DL': - return DoorLockControls(device: device); + return DoorLockControlsView(device: device); case 'WPS': - return WallSensorControls(device: device); + return WallSensorControlsView(device: device); case 'CPS': - return CeilingSensorControls( + return CeilingSensorControlsView( device: device, ); case 'CUR': - return CurtainStatusControls( + return CurtainStatusControlsView( deviceId: device.uuid!, ); case 'AC': - return AcDeviceControls(device: device); + return AcDeviceControlsView(device: device); default: return const SizedBox(); } } /* - 3G: 2 occurrences - 1G: 1 occurrence - 2G: 1 occurrence - GW: 2 occurrences - DL: 2 occurrences - WPS: 2 occurrences - CPS: 2 occurrences - AC: 3 occurrences - CUR: 1 occurrence - - + 3G: + 1G: + 2G: + GW: + DL: + WPS: + CPS: + AC: + CUR: */ Widget routeBatchControlsWidgets({required List devices}) { switch (devices.first.productType) { - case '3G': case '1G': - case '2G': - return LivingRoomBatchControls( + return WallLightBatchControlView( deviceIds: devices - .where((e) => (e.productType == '3G' || - e.productType == '1G' || - e.productType == '2G')) + .where((e) => (e.productType == '1G')) + .map((e) => e.uuid!) + .toList(), + ); + case '2G': + return TwoGangBatchControlView( + deviceIds: devices + .where((e) => (e.productType == '2G')) + .map((e) => e.uuid!) + .toList(), + ); + case '3G': + return LivingRoomBatchControlsView( + deviceIds: devices + .where((e) => (e.productType == '3G' || e.productType == '2G')) .map((e) => e.uuid!) .toList(), ); case 'GW': - return GatewayBatchControl( + return GatewayBatchControlView( gatewayIds: devices .where((e) => (e.productType == 'GW')) .map((e) => e.uuid!) @@ -87,14 +107,14 @@ mixin RouteControlsBasedCode { .map((e) => e.uuid!) .toList()); case 'WPS': - return WallSensorBatchControl( + return WallSensorBatchControlView( devicesIds: devices .where((e) => (e.productType == 'WPS')) .map((e) => e.uuid!) .toList()); case 'CPS': - return CeilingSensorBatchControl( - devicesIds: devices + return CeilingSensorBatchControlView( + devicesIds: devices .where((e) => (e.productType == 'CPS')) .map((e) => e.uuid!) .toList(), @@ -107,7 +127,7 @@ mixin RouteControlsBasedCode { .toList(), ); case 'AC': - return AcDeviceBatchControl( + return AcDeviceBatchControlView( devicesIds: devices .where((e) => (e.productType == 'AC')) .map((e) => e.uuid!) diff --git a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart index ca52c148..5511c0bc 100644 --- a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart +++ b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart @@ -11,9 +11,9 @@ import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presen import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class CeilingSensorBatchControl extends StatelessWidget +class CeilingSensorBatchControlView extends StatelessWidget with HelperResponsiveLayout { - const CeilingSensorBatchControl({super.key, required this.devicesIds}); + const CeilingSensorBatchControlView({super.key, required this.devicesIds}); final List devicesIds; diff --git a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart index bf24ae0f..1f4d58f9 100644 --- a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart +++ b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart @@ -16,9 +16,9 @@ import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dar import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class CeilingSensorControls extends StatelessWidget +class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLayout { - const CeilingSensorControls({super.key, required this.device}); + const CeilingSensorControlsView({super.key, required this.device}); final AllDevicesModel device; diff --git a/lib/pages/device_managment/curtain/view/curtain_status_view.dart b/lib/pages/device_managment/curtain/view/curtain_status_view.dart index 1de7fa83..7e1eed1d 100644 --- a/lib/pages/device_managment/curtain/view/curtain_status_view.dart +++ b/lib/pages/device_managment/curtain/view/curtain_status_view.dart @@ -6,11 +6,11 @@ import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.da import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class CurtainStatusControls extends StatelessWidget +class CurtainStatusControlsView extends StatelessWidget with HelperResponsiveLayout { final String deviceId; - const CurtainStatusControls({super.key, required this.deviceId}); + const CurtainStatusControlsView({super.key, required this.deviceId}); @override Widget build(BuildContext context) { diff --git a/lib/pages/device_managment/door_lock/view/door_lock_control_view.dart b/lib/pages/device_managment/door_lock/view/door_lock_control_view.dart index 6e24907a..346da6bf 100644 --- a/lib/pages/device_managment/door_lock/view/door_lock_control_view.dart +++ b/lib/pages/device_managment/door_lock/view/door_lock_control_view.dart @@ -7,10 +7,10 @@ import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_stat import 'package:syncrow_web/pages/device_managment/door_lock/models/door_lock_status_model.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/widget/door_button.dart'; -class DoorLockControls extends StatelessWidget { +class DoorLockControlsView extends StatelessWidget { final AllDevicesModel device; - const DoorLockControls({super.key, required this.device}); + const DoorLockControlsView({super.key, required this.device}); @override Widget build(BuildContext context) { diff --git a/lib/pages/device_managment/gateway/view/gateway_batch_control.dart b/lib/pages/device_managment/gateway/view/gateway_batch_control.dart index 27e14998..f303cdba 100644 --- a/lib/pages/device_managment/gateway/view/gateway_batch_control.dart +++ b/lib/pages/device_managment/gateway/view/gateway_batch_control.dart @@ -5,8 +5,9 @@ import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_ import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class GatewayBatchControl extends StatelessWidget with HelperResponsiveLayout { - const GatewayBatchControl({super.key, required this.gatewayIds}); +class GatewayBatchControlView extends StatelessWidget + with HelperResponsiveLayout { + const GatewayBatchControlView({super.key, required this.gatewayIds}); final List gatewayIds; diff --git a/lib/pages/device_managment/gateway/view/gateway_view.dart b/lib/pages/device_managment/gateway/view/gateway_view.dart index 9fbe8cb9..718e4d84 100644 --- a/lib/pages/device_managment/gateway/view/gateway_view.dart +++ b/lib/pages/device_managment/gateway/view/gateway_view.dart @@ -7,8 +7,8 @@ import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class GateWayControls extends StatelessWidget with HelperResponsiveLayout { - const GateWayControls({super.key, required this.gatewayId}); +class GateWayControlsView extends StatelessWidget with HelperResponsiveLayout { + const GateWayControlsView({super.key, required this.gatewayId}); final String gatewayId; diff --git a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart new file mode 100644 index 00000000..19517c92 --- /dev/null +++ b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart @@ -0,0 +1,120 @@ +import 'dart:async'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +class WallLightSwitchBloc + extends Bloc { + WallLightSwitchBloc({required this.deviceId}) + : super(WallLightSwitchInitial()) { + on(_onFetchDeviceStatus); + on(_onControl); + on(_onFetchBatchStatus); + } + + late WallLightStatusModel deviceStatus; + final String deviceId; + Timer? _timer; + + FutureOr _onFetchDeviceStatus(WallLightSwitchFetchDeviceEvent event, + Emitter emit) async { + emit(WallLightSwitchLoading()); + try { + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + + deviceStatus = + WallLightStatusModel.fromJson(event.deviceId, status.status); + emit(WallLightSwitchStatusLoaded(deviceStatus)); + } catch (e) { + emit(WallLightSwitchError(e.toString())); + } + } + + FutureOr _onControl( + WallLightSwitchControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + + emit(WallLightSwitchStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + ); + } + + Future _runDebounce({ + required String deviceId, + required String code, + required bool value, + required bool oldValue, + required Emitter emit, + }) async { + if (_timer != null) { + _timer!.cancel(); + } + + _timer = Timer(const Duration(milliseconds: 500), () async { + try { + final status = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + + if (!status) { + _revertValueAndEmit(deviceId, code, oldValue, emit); + } + } catch (e) { + _revertValueAndEmit(deviceId, code, oldValue, emit); + } + }); + } + + void _revertValueAndEmit(String deviceId, String code, bool oldValue, + Emitter emit) { + _updateLocalValue(code, oldValue); + emit(WallLightSwitchStatusLoaded(deviceStatus)); + } + + void _updateLocalValue(String code, bool value) { + if (code == 'switch_1') { + deviceStatus = deviceStatus.copyWith(switch1: value); + } + } + + bool _getValueByCode(String code) { + switch (code) { + case 'switch_1': + return deviceStatus.switch1; + default: + return false; + } + } + + Future _onFetchBatchStatus(WallLightSwitchFetchBatchEvent event, + Emitter emit) async { + emit(WallLightSwitchLoading()); + try { + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + WallLightStatusModel.fromJson(event.deviceId, status.status); + emit(WallLightSwitchStatusLoaded(deviceStatus)); + } catch (e) { + emit(WallLightSwitchError(e.toString())); + } + } + + @override + Future close() { + _timer?.cancel(); + return super.close(); + } +} diff --git a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart new file mode 100644 index 00000000..59842692 --- /dev/null +++ b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart @@ -0,0 +1,48 @@ +import 'package:equatable/equatable.dart'; + +class WallLightSwitchEvent extends Equatable { + @override + List get props => []; +} + +class WallLightSwitchFetchDeviceEvent extends WallLightSwitchEvent { + final String deviceId; + + WallLightSwitchFetchDeviceEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class WallLightSwitchControl extends WallLightSwitchEvent { + final String deviceId; + final String code; + final bool value; + + WallLightSwitchControl( + {required this.deviceId, required this.code, required this.value}); + + @override + List get props => [deviceId, code, value]; +} + +class WallLightSwitchFetchBatchEvent extends WallLightSwitchEvent { + final String deviceId; + + WallLightSwitchFetchBatchEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class WallLightSwitchBatchControl extends WallLightSwitchEvent { + final List deviceId; + final String code; + final bool value; + + WallLightSwitchBatchControl( + {required this.deviceId, required this.code, required this.value}); + + @override + List get props => [deviceId, code, value]; +} diff --git a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart new file mode 100644 index 00000000..69cdf05e --- /dev/null +++ b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart @@ -0,0 +1,56 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart'; + +class WallLightSwitchState extends Equatable { + @override + List get props => []; +} + +class WallLightSwitchInitial extends WallLightSwitchState {} + +class WallLightSwitchLoading extends WallLightSwitchState {} + +class WallLightSwitchStatusLoaded extends WallLightSwitchState { + final WallLightStatusModel status; + + WallLightSwitchStatusLoaded(this.status); + + @override + List get props => [status]; +} + +class WallLightSwitchError extends WallLightSwitchState { + final String message; + + WallLightSwitchError(this.message); + + @override + List get props => [message]; +} + +class WallLightSwitchControlError extends WallLightSwitchState { + final String message; + + WallLightSwitchControlError(this.message); + + @override + List get props => [message]; +} + +class WallLightSwitchBatchControlError extends WallLightSwitchState { + final String message; + + WallLightSwitchBatchControlError(this.message); + + @override + List get props => [message]; +} + +class WallLightSwitchBatchStatusLoaded extends WallLightSwitchState { + final List status; + + WallLightSwitchBatchStatusLoaded(this.status); + + @override + List get props => [status]; +} diff --git a/lib/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart b/lib/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart new file mode 100644 index 00000000..b479c71d --- /dev/null +++ b/lib/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart @@ -0,0 +1,47 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class WallLightStatusModel { + final String uuid; + final bool switch1; + final int countDown; + + WallLightStatusModel({ + required this.uuid, + required this.switch1, + required this.countDown, + }); + + factory WallLightStatusModel.fromJson(String id, List jsonList) { + late bool switch1; + late int countDown; + + for (var status in jsonList) { + switch (status.code) { + case 'switch_1': + switch1 = status.value ?? false; + break; + case 'countdown_1': + countDown = status.value ?? 0; + break; + } + } + + return WallLightStatusModel( + uuid: id, + switch1: switch1, + countDown: countDown, + ); + } + + WallLightStatusModel copyWith({ + String? uuid, + bool? switch1, + int? countDown, + }) { + return WallLightStatusModel( + uuid: uuid ?? this.uuid, + switch1: switch1 ?? this.switch1, + countDown: countDown ?? this.countDown, + ); + } +} diff --git a/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart b/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart new file mode 100644 index 00000000..78de7658 --- /dev/null +++ b/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class WallLightBatchControlView extends StatelessWidget + with HelperResponsiveLayout { + const WallLightBatchControlView({super.key, required this.deviceIds}); + + final List deviceIds; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => WallLightSwitchBloc(deviceId: deviceIds.first) + ..add(WallLightSwitchFetchBatchEvent(deviceIds.first)), + child: BlocBuilder( + builder: (context, state) { + if (state is WallLightSwitchLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is WallLightSwitchStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is WallLightSwitchError || + state is WallLightSwitchControlError) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls( + BuildContext context, WallLightStatusModel status) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return SizedBox( + child: 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, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceIds.first, + label: 'Wall Light', + onChange: (value) {}, + ), + FirmwareUpdateWidget( + deviceId: deviceIds.first, + version: 12, + ), + FactoryResetWidget(deviceId: deviceIds.first), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart b/lib/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart new file mode 100644 index 00000000..85a4221e --- /dev/null +++ b/lib/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart'; +import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart'; +import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class WallLightDeviceControl extends StatelessWidget + with HelperResponsiveLayout { + final String deviceId; + + const WallLightDeviceControl({super.key, required this.deviceId}); + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => WallLightSwitchBloc(deviceId: deviceId) + ..add(WallLightSwitchFetchDeviceEvent(deviceId)), + child: BlocBuilder( + builder: (context, state) { + if (state is WallLightSwitchLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is WallLightSwitchStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is WallLightSwitchError || + state is WallLightSwitchControlError) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls( + BuildContext context, WallLightStatusModel 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, + ), + children: [ + const SizedBox(), + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceId, + label: 'Wall Light', + onChange: (value) { + context.read().add(WallLightSwitchControl( + deviceId: deviceId, + code: 'switch_1', + value: value, + )); + }, + ), + const SizedBox(), + ], + ); + } +} diff --git a/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart b/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart index 39cf84f1..43a5b0c3 100644 --- a/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart +++ b/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart @@ -37,3 +37,15 @@ class LivingRoomControl extends LivingRoomEvent { @override List get props => [deviceId, code, value]; } + +class LivingRoomBatchControl extends LivingRoomEvent { + final List deviceId; + final String code; + final bool value; + + const LivingRoomBatchControl( + {required this.deviceId, required this.code, required this.value}); + + @override + List get props => [deviceId, code, value]; +} diff --git a/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart index b07ecbff..f70605b8 100644 --- a/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart +++ b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart @@ -7,9 +7,9 @@ import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/livi import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class LivingRoomBatchControls extends StatelessWidget +class LivingRoomBatchControlsView extends StatelessWidget with HelperResponsiveLayout { - const LivingRoomBatchControls({super.key, required this.deviceIds}); + const LivingRoomBatchControlsView({super.key, required this.deviceIds}); final List deviceIds; @@ -61,6 +61,7 @@ class LivingRoomBatchControls extends StatelessWidget code: 'switch_1', deviceId: deviceIds.first, label: 'Wall Light', + onChange: (value) {}, ), FirmwareUpdateWidget( deviceId: deviceIds.first, diff --git a/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart b/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart index a0588d8d..07332217 100644 --- a/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart +++ b/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart @@ -5,11 +5,11 @@ import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/livi import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class LivingRoomDeviceControls extends StatelessWidget +class LivingRoomDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { final String deviceId; - const LivingRoomDeviceControls({super.key, required this.deviceId}); + const LivingRoomDeviceControlsView({super.key, required this.deviceId}); @override Widget build(BuildContext context) { @@ -38,42 +38,58 @@ class LivingRoomDeviceControls extends StatelessWidget final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); - return Container( - child: 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, - ), - children: [ - ToggleWidget( - value: status.switch1, - code: 'switch_1', - deviceId: deviceId, - label: 'Wall Light', - ), - ToggleWidget( - value: status.switch2, - code: 'switch_2', - deviceId: deviceId, - label: 'Ceiling Light', - ), - ToggleWidget( - value: status.switch3, - code: 'switch_3', - deviceId: deviceId, - label: 'Spotlight', - ), - ], + 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, ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceId, + label: 'Wall Light', + onChange: (value) { + context.read().add( + LivingRoomControl( + deviceId: deviceId, code: 'switch_1', value: value), + ); + }, + ), + ToggleWidget( + value: status.switch2, + code: 'switch_2', + deviceId: deviceId, + label: 'Ceiling Light', + onChange: (value) { + context.read().add( + LivingRoomControl( + deviceId: deviceId, code: 'switch_2', value: value), + ); + }, + ), + ToggleWidget( + value: status.switch3, + code: 'switch_3', + deviceId: deviceId, + label: 'Spotlight', + onChange: (value) { + context.read().add( + LivingRoomControl( + deviceId: deviceId, code: 'switch_3', value: value), + ); + }, + ), + ], ); } } diff --git a/lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart b/lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart index 62306a92..25c14511 100644 --- a/lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart +++ b/lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart @@ -1,8 +1,6 @@ import 'package:flutter/cupertino.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/device_managment/three_gang_switch/bloc/living_room_bloc.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'; @@ -12,6 +10,7 @@ class ToggleWidget extends StatelessWidget { final String code; final String deviceId; final String label; + final Function(dynamic value) onChange; const ToggleWidget({ super.key, @@ -19,6 +18,7 @@ class ToggleWidget extends StatelessWidget { required this.code, required this.deviceId, required this.label, + required this.onChange, }); @override @@ -64,15 +64,7 @@ class ToggleWidget extends StatelessWidget { child: CupertinoSwitch( value: value, activeColor: ColorsManager.dialogBlueTitle, - onChanged: (newValue) { - context.read().add( - LivingRoomControl( - deviceId: deviceId, - code: code, - value: newValue, - ), - ); - }, + onChanged: onChange, ), ), ], diff --git a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart new file mode 100644 index 00000000..98ce8071 --- /dev/null +++ b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart @@ -0,0 +1,125 @@ +import 'dart:async'; + +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +class TwoGangSwitchBloc extends Bloc { + TwoGangSwitchBloc({required this.deviceId}) + : super(TwoGangSwitchInitial()) { + on(_onFetchDeviceStatus); + on(_onControl); + on(_onFetchBatchStatus); + } + + late TwoGangStatusModel deviceStatus; + final String deviceId; + Timer? _timer; + + FutureOr _onFetchDeviceStatus(TwoGangSwitchFetchDeviceEvent event, + Emitter emit) async { + emit(TwoGangSwitchLoading()); + try { + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + + deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status); + emit(TwoGangSwitchStatusLoaded(deviceStatus)); + } catch (e) { + emit(TwoGangSwitchError(e.toString())); + } + } + + FutureOr _onControl( + TwoGangSwitchControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + + emit(TwoGangSwitchStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + ); + } + + Future _runDebounce({ + required String deviceId, + required String code, + required bool value, + required bool oldValue, + required Emitter emit, + }) async { + if (_timer != null) { + _timer!.cancel(); + } + + _timer = Timer(const Duration(milliseconds: 500), () async { + try { + final status = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + + if (!status) { + _revertValueAndEmit(deviceId, code, oldValue, emit); + } + } catch (e) { + _revertValueAndEmit(deviceId, code, oldValue, emit); + } + }); + } + + void _revertValueAndEmit(String deviceId, String code, bool oldValue, + Emitter emit) { + _updateLocalValue(code, oldValue); + emit(TwoGangSwitchStatusLoaded(deviceStatus)); + } + + void _updateLocalValue(String code, bool value) { + if (code == 'switch_1') { + deviceStatus = deviceStatus.copyWith(switch1: value); + } + + if (code == 'switch_2') { + deviceStatus = deviceStatus.copyWith(switch2: value); + } + + } + + bool _getValueByCode(String code) { + switch (code) { + case 'switch_1': + return deviceStatus.switch1; + case 'switch_2': + return deviceStatus.switch2; + default: + return false; + } + } + + Future _onFetchBatchStatus( TwoGangSwitchFetchBatchEvent event, + Emitter< TwoGangSwitchState> emit) async { + emit(TwoGangSwitchLoading()); + try { + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + TwoGangStatusModel.fromJson(event.deviceId, status.status); + emit(TwoGangSwitchStatusLoaded(deviceStatus)); + } catch (e) { + emit(TwoGangSwitchError(e.toString())); + } + } + + @override + Future close() { + _timer?.cancel(); + return super.close(); + } +} diff --git a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart new file mode 100644 index 00000000..2bacfc92 --- /dev/null +++ b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart @@ -0,0 +1,48 @@ +import 'package:equatable/equatable.dart'; + +class TwoGangSwitchEvent extends Equatable { + @override + List get props => []; +} + +class TwoGangSwitchFetchDeviceEvent extends TwoGangSwitchEvent { + final String deviceId; + + TwoGangSwitchFetchDeviceEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class TwoGangSwitchControl extends TwoGangSwitchEvent { + final String deviceId; + final String code; + final bool value; + + TwoGangSwitchControl( + {required this.deviceId, required this.code, required this.value}); + + @override + List get props => [deviceId, code, value]; +} + +class TwoGangSwitchFetchBatchEvent extends TwoGangSwitchEvent { + final String deviceId; + + TwoGangSwitchFetchBatchEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class TwoGangSwitchBatchControl extends TwoGangSwitchEvent { + final List deviceId; + final String code; + final bool value; + + TwoGangSwitchBatchControl( + {required this.deviceId, required this.code, required this.value}); + + @override + List get props => [deviceId, code, value]; +} diff --git a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart new file mode 100644 index 00000000..b9208211 --- /dev/null +++ b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart @@ -0,0 +1,56 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart'; + +class TwoGangSwitchState extends Equatable { + @override + List get props => []; +} + +class TwoGangSwitchInitial extends TwoGangSwitchState {} + +class TwoGangSwitchLoading extends TwoGangSwitchState {} + +class TwoGangSwitchStatusLoaded extends TwoGangSwitchState { + final TwoGangStatusModel status; + + TwoGangSwitchStatusLoaded(this.status); + + @override + List get props => [status]; +} + +class TwoGangSwitchError extends TwoGangSwitchState { + final String message; + + TwoGangSwitchError(this.message); + + @override + List get props => [message]; +} + +class TwoGangSwitchControlError extends TwoGangSwitchState { + final String message; + + TwoGangSwitchControlError(this.message); + + @override + List get props => [message]; +} + +class TwoGangSwitchBatchControlError extends TwoGangSwitchState { + final String message; + + TwoGangSwitchBatchControlError(this.message); + + @override + List get props => [message]; +} + +class TwoGangSwitchBatchStatusLoaded extends TwoGangSwitchState { + final List status; + + TwoGangSwitchBatchStatusLoaded(this.status); + + @override + List get props => [status]; +} diff --git a/lib/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart b/lib/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart new file mode 100644 index 00000000..6cec4256 --- /dev/null +++ b/lib/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart @@ -0,0 +1,65 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class TwoGangStatusModel { + final String uuid; + final bool switch1; + final bool switch2; + final int countDown; + final int countDown2; + + TwoGangStatusModel({ + required this.uuid, + required this.switch1, + required this.switch2, + required this.countDown, + required this.countDown2, + }); + + factory TwoGangStatusModel.fromJson(String id, List jsonList) { + late bool switch1; + late bool switch2; + late int countDown; + late int countDown2; + + for (var status in jsonList) { + switch (status.code) { + case 'switch_1': + switch1 = status.value ?? false; + break; + case 'countdown_1': + countDown = status.value ?? 0; + break; + case 'switch_2': + switch2 = status.value ?? false; + break; + case 'countdown_2': + countDown2 = status.value ?? 0; + break; + } + } + + return TwoGangStatusModel( + uuid: id, + switch1: switch1, + countDown: countDown, + switch2: switch2, + countDown2: countDown2, + ); + } + + TwoGangStatusModel copyWith({ + String? uuid, + bool? switch1, + int? countDown, + bool? switch2, + int? countDown2, + }) { + return TwoGangStatusModel( + uuid: uuid ?? this.uuid, + switch1: switch1 ?? this.switch1, + countDown: countDown ?? this.countDown, + switch2: switch2 ?? this.switch2, + countDown2: countDown2 ?? this.countDown2, + ); + } +} diff --git a/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart b/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart new file mode 100644 index 00000000..4926911a --- /dev/null +++ b/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class TwoGangBatchControlView extends StatelessWidget + with HelperResponsiveLayout { + const TwoGangBatchControlView({super.key, required this.deviceIds}); + + final List deviceIds; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => TwoGangSwitchBloc(deviceId: deviceIds.first) + ..add(TwoGangSwitchFetchBatchEvent(deviceIds.first)), + child: BlocBuilder( + builder: (context, state) { + if (state is TwoGangSwitchLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is TwoGangSwitchStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is TwoGangSwitchError || + state is TwoGangSwitchControlError) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls(BuildContext context, TwoGangStatusModel status) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return SizedBox( + child: 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, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceIds.first, + label: 'Wall Light', + onChange: (value) {}, + ), + ToggleWidget( + value: status.switch2, + code: 'switch_2', + deviceId: deviceIds.first, + label: 'Ceiling Light', + onChange: (value) {}, + ), + FirmwareUpdateWidget( + deviceId: deviceIds.first, + version: 12, + ), + FactoryResetWidget(deviceId: deviceIds.first), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart b/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart new file mode 100644 index 00000000..fa42d096 --- /dev/null +++ b/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart'; +import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class TwoGangDeviceControlView extends StatelessWidget + with HelperResponsiveLayout { + final String deviceId; + + const TwoGangDeviceControlView({super.key, required this.deviceId}); + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => TwoGangSwitchBloc(deviceId: deviceId) + ..add(TwoGangSwitchFetchDeviceEvent(deviceId)), + child: BlocBuilder( + builder: (context, state) { + if (state is TwoGangSwitchLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is TwoGangSwitchStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is TwoGangSwitchError || + state is TwoGangSwitchControlError) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + 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, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceId, + label: 'Wall Light', + onChange: (value) { + context.read().add(TwoGangSwitchControl( + deviceId: deviceId, + code: 'switch_1', + value: value, + )); + }, + ), + ToggleWidget( + value: status.switch2, + code: 'switch_2', + deviceId: deviceId, + label: 'Ceiling Light', + onChange: (value) { + context.read().add(TwoGangSwitchControl( + deviceId: deviceId, + code: 'switch_2', + value: value, + )); + }, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart index 88d1270a..703d129e 100644 --- a/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart +++ b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart @@ -9,9 +9,9 @@ import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/state.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class WallSensorBatchControl extends StatelessWidget +class WallSensorBatchControlView extends StatelessWidget with HelperResponsiveLayout { - const WallSensorBatchControl({super.key, required this.devicesIds}); + const WallSensorBatchControlView({super.key, required this.devicesIds}); final List devicesIds; diff --git a/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart b/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart index fce90593..375b01be 100644 --- a/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart +++ b/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart @@ -14,8 +14,9 @@ import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class WallSensorControls extends StatelessWidget with HelperResponsiveLayout { - const WallSensorControls({super.key, required this.device}); +class WallSensorControlsView extends StatelessWidget + with HelperResponsiveLayout { + const WallSensorControlsView({super.key, required this.device}); final AllDevicesModel device; From b18b96064ae00b01eeac75a847a1c2f448ba12d7 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 16 Sep 2024 11:03:40 +0300 Subject: [PATCH 09/65] push 1g, 2g --- .../all_devices/helper/route_controls_based_code.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 1fb82348..9c893983 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -89,7 +89,7 @@ mixin RouteControlsBasedCode { case '3G': return LivingRoomBatchControlsView( deviceIds: devices - .where((e) => (e.productType == '3G' || e.productType == '2G')) + .where((e) => (e.productType == '3G')) .map((e) => e.uuid!) .toList(), ); From 60b51657a6a1e4db5b6e132463f92064eeeb8731 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 17 Sep 2024 00:22:51 +0300 Subject: [PATCH 10/65] working on water heater --- .../bloc/device_managment_bloc.dart | 8 ++ .../helper/route_controls_based_code.dart | 3 + .../all_devices/models/fake_door_sensor.dart | 33 +++++ .../models/fake_heater_device.dart | 33 +++++ .../widgets/living_toggle_widget.dart | 4 +- .../water_heater/bloc/water_heater_bloc.dart | 132 ++++++++++++++++++ .../water_heater/bloc/water_heater_event.dart | 49 +++++++ .../water_heater/bloc/water_heater_state.dart | 76 ++++++++++ .../models/water_heater_status_model.dart | 83 +++++++++++ .../view/water_heater_device_control.dart | 73 ++++++++++ 10 files changed, 493 insertions(+), 1 deletion(-) create mode 100644 lib/pages/device_managment/all_devices/models/fake_door_sensor.dart create mode 100644 lib/pages/device_managment/all_devices/models/fake_heater_device.dart create mode 100644 lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart create mode 100644 lib/pages/device_managment/water_heater/bloc/water_heater_event.dart create mode 100644 lib/pages/device_managment/water_heater/bloc/water_heater_state.dart create mode 100644 lib/pages/device_managment/water_heater/models/water_heater_status_model.dart create mode 100644 lib/pages/device_managment/water_heater/view/water_heater_device_control.dart diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart index 70c65fcc..c3104c65 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart @@ -1,7 +1,10 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/fake_door_sensor.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; + +import '../models/fake_heater_device.dart'; part 'device_managment_event.dart'; part 'device_managment_state.dart'; @@ -31,6 +34,11 @@ class DeviceManagementBloc try { final devices = await DevicesManagementApi().fetchDevices(); _selectedDevices.clear(); + + /// add fake device for heater + devices.insert(0, fakeWaterHeaterDevice); + devices.insert(1, fakeMainDoorSensor); + //// _devices = devices; _filteredDevices = devices; _calculateDeviceCounts(); diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 9c893983..2872ee6d 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -19,6 +19,7 @@ import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_lig import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_device_control.dart'; mixin RouteControlsBasedCode { Widget routeControlsWidgets({required AllDevicesModel device}) { @@ -53,6 +54,8 @@ mixin RouteControlsBasedCode { ); case 'AC': return AcDeviceControlsView(device: device); + case 'WH': + return WaterHeaterDeviceControl(device: device,); default: return const SizedBox(); } diff --git a/lib/pages/device_managment/all_devices/models/fake_door_sensor.dart b/lib/pages/device_managment/all_devices/models/fake_door_sensor.dart new file mode 100644 index 00000000..6682de18 --- /dev/null +++ b/lib/pages/device_managment/all_devices/models/fake_door_sensor.dart @@ -0,0 +1,33 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; + +AllDevicesModel fakeMainDoorSensor = AllDevicesModel( + room: DevicesModelRoom( + uuid: "12de8f60-7104-4726-b5f8-ea426c0c7c3d", name: "Main Hall"), + unit: DevicesModelUnit( + uuid: "08fd3dcf-d13a-40db-970d-d0ce893df30e", name: "Entrance Unit 1"), + productUuid: "fake-uuid-main-door-sensor", + productType: "DS", + permissionType: "CONTROLLABLE", + activeTime: 1722178888, + category: "sensor", + categoryName: "Door Sensor", + createTime: 1722178888, + gatewayId: "b49df7395gfd8c19047krmk", + icon: "smart/icon/bay1642572935122vdsS/2b2f5fffaa5bbf81c3164fc313df2023.png", + ip: "", + lat: "31.92", + localKey: "A/43+:7M", + lon: "35.85", + model: "D03ZLSDSA2", + name: "Main Door Sensor", + nodeId: "70a523ffece8a7f9", + online: true, + ownerId: "199300932", + sub: true, + timeZone: "+03:00", + updateTime: 1723627123, + uuid: "9c32dac5-ce0c-4c85-b45c-8e16511174cg", + batteryLevel: 85, +); diff --git a/lib/pages/device_managment/all_devices/models/fake_heater_device.dart b/lib/pages/device_managment/all_devices/models/fake_heater_device.dart new file mode 100644 index 00000000..8ce1bb90 --- /dev/null +++ b/lib/pages/device_managment/all_devices/models/fake_heater_device.dart @@ -0,0 +1,33 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; + +AllDevicesModel fakeWaterHeaterDevice = AllDevicesModel( + room: DevicesModelRoom( + uuid: "75ea7d60-5104-4726-b5f8-ea426c0c6a1b", name: "Kitchen"), + unit: DevicesModelUnit( + uuid: "04fd1dcf-f24a-40db-970d-d0be884ed30f", name: "Flat 101"), + productUuid: "fake-uuid-kitchen-water-heater", + productType: "WH", + permissionType: "CONTROLLABLE", + activeTime: 1722173778, + category: "kg", + categoryName: "Water Heater", + createTime: 1722173778, + gatewayId: "bf0294123ed2c19067skrk", + icon: "smart/icon/bay1642572935385vcsA/2b1f5efbaa5bbf81c3164fa312cf2032.png", + ip: "", + lat: "31.97", + localKey: "T/39+:9M", + lon: "35.89", + model: "S01ZLSWBSA3", + name: "Kitchen Water Heater", + nodeId: "60a423fffed5a7f6", + online: true, + ownerId: "199200732", + sub: true, + timeZone: "+03:00", + updateTime: 1723626515, + uuid: "5b31dae4-ce9c-4c70-b52b-7e150654sdf56", + batteryLevel: null, +); diff --git a/lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart b/lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart index 25c14511..71eba26f 100644 --- a/lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart +++ b/lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart @@ -10,6 +10,7 @@ class ToggleWidget extends StatelessWidget { final String code; final String deviceId; final String label; + final String? icon; final Function(dynamic value) onChange; const ToggleWidget({ @@ -19,6 +20,7 @@ class ToggleWidget extends StatelessWidget { required this.deviceId, required this.label, required this.onChange, + this.icon, }); @override @@ -42,7 +44,7 @@ class ToggleWidget extends StatelessWidget { child: Container( color: ColorsManager.whiteColors, child: SvgPicture.asset( - Assets.lightPulp, + icon ?? Assets.lightPulp, width: 60, height: 60, fit: BoxFit.cover, diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart new file mode 100644 index 00000000..47632653 --- /dev/null +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -0,0 +1,132 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; + +part 'water_heater_event.dart'; +part 'water_heater_state.dart'; + +class WaterHeaterBloc extends Bloc { + WaterHeaterBloc() : super(WaterHeaterInitial()) { + on(_fetchWaterHeaterStatus); + on(_controlWaterHeater); + on(_updateScheduleEvent); + on(_stopScheduleEvent); + } + + late WaterHeaterStatusModel deviceStatus; + Timer? _timer; + + FutureOr _updateScheduleEvent( + UpdateScheduleEvent event, + Emitter emit, + ) { + emit(WaterHeaterScheduleState( + scheduleType: event.scheduleType, + hours: event.hours, + minutes: event.minutes, + isActive: true, + )); + } + + FutureOr _stopScheduleEvent( + StopScheduleEvent event, + Emitter emit, + ) { + if (state is WaterHeaterScheduleState) { + final currentState = state as WaterHeaterScheduleState; + emit(WaterHeaterScheduleState( + scheduleType: currentState.scheduleType, + hours: currentState.hours, + minutes: currentState.minutes, + isActive: false, + )); + } + } + + FutureOr _controlWaterHeater( + ToggleWaterHeaterEvent event, Emitter emit) async { + final oldValue = deviceStatus.heaterSwitch; + + _updateLocalValue(event.isOn); + + emit(WaterHeaterToggleState(isOn: event.isOn)); + + await _runDebounce( + deviceId: event.deviceId, + value: event.isOn, + oldValue: oldValue, + emit: emit, + ); + } + + Future _runDebounce({ + required String deviceId, + required bool value, + required bool oldValue, + required Emitter emit, + }) async { + if (_timer != null) { + _timer!.cancel(); + } + + _timer = Timer(const Duration(milliseconds: 500), () async { + try { + //// TODO: implement control + + // final status = await DevicesManagementApi().deviceControl( + // deviceId, Status(value: value, code: 'heaterSwitch')); + + // if (!status) { + // _revertValueAndEmit(deviceId, oldValue, emit); + // } + } catch (e) { + _revertValueAndEmit(deviceId, oldValue, emit); + } + }); + } + + void _revertValueAndEmit( + String deviceId, bool oldValue, Emitter emit) { + _updateLocalValue(oldValue); + emit(WaterHeaterToggleState(isOn: oldValue)); + } + + void _updateLocalValue(bool value) { + deviceStatus = deviceStatus.copyWith(heaterSwitch: value); + } + + FutureOr _fetchWaterHeaterStatus( + WaterHeaterFetchStatusEvent event, Emitter emit) async { + emit(WaterHeaterLoadingState()); + + try { + // final status = + // await DevicesManagementApi().getDeviceStatus(event.deviceId); + // deviceStatus = + // WaterHeaterStatusModel.fromJson(event.deviceId, status.status); + + final List fakeStatusList = [ + Status(code: 'switch', value: true), + Status(code: 'schedule_mode', value: 'countdown'), + Status(code: 'countdown_hours', value: 6), + Status(code: 'countdown_minutes', value: 23), + ]; + + final fakeWaterHeaterStatus = + WaterHeaterStatusModel.fromJson(event.deviceId, fakeStatusList); + + emit(WaterHeaterDeviceStatusLoaded(fakeWaterHeaterStatus)); + } catch (e) { + emit(WaterHeaterFailedState(error: e.toString())); + } + } + + @override + Future close() { + _timer?.cancel(); + return super.close(); + } +} diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart new file mode 100644 index 00000000..3adf23dd --- /dev/null +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -0,0 +1,49 @@ +part of 'water_heater_bloc.dart'; + +sealed class WaterHeaterEvent extends Equatable { + const WaterHeaterEvent(); + + @override + List get props => []; +} + +final class ToggleWaterHeaterEvent extends WaterHeaterEvent { + final bool isOn; + final String deviceId; + + const ToggleWaterHeaterEvent({required this.isOn, required this.deviceId}); + + @override + List get props => [isOn]; +} + +final class UpdateScheduleEvent extends WaterHeaterEvent { + final ScheduleType scheduleType; + final int hours; + final int minutes; + + const UpdateScheduleEvent(this.scheduleType, this.hours, this.minutes); + + @override + List get props => [scheduleType, hours, minutes]; +} + +final class StopScheduleEvent extends WaterHeaterEvent {} + +class WaterHeaterFetchStatusEvent extends WaterHeaterEvent { + final String deviceId; + + const WaterHeaterFetchStatusEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class WaterHeaterFetchBatchStatusEvent extends WaterHeaterEvent { + final String deviceId; + + const WaterHeaterFetchBatchStatusEvent(this.deviceId); + + @override + List get props => [deviceId]; +} diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart new file mode 100644 index 00000000..bc9ac051 --- /dev/null +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart @@ -0,0 +1,76 @@ +part of 'water_heater_bloc.dart'; + +enum ScheduleType { countdown, schedule, circulate, inching } + +sealed class WaterHeaterState extends Equatable { + const WaterHeaterState(); + + @override + List get props => []; +} + +final class WaterHeaterInitial extends WaterHeaterState {} + +final class WaterHeaterToggleState extends WaterHeaterState { + final bool isOn; + + const WaterHeaterToggleState({required this.isOn}); + + @override + List get props => [isOn]; +} + +final class WaterHeaterScheduleState extends WaterHeaterState { + final ScheduleType scheduleType; + final int hours; + final int minutes; + final bool isActive; + + const WaterHeaterScheduleState({ + required this.scheduleType, + required this.hours, + required this.minutes, + required this.isActive, + }); + + @override + List get props => [scheduleType, hours, minutes, isActive]; +} + +final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { + final WaterHeaterStatusModel status; + + const WaterHeaterDeviceStatusLoaded(this.status); + + @override + List get props => [status]; +} + +final class WaterHeaterBatchStatusLoaded extends WaterHeaterState { + final WaterHeaterStatusModel status; + + const WaterHeaterBatchStatusLoaded(this.status); + + @override + List get props => [status]; +} + +final class WaterHeaterFailedState extends WaterHeaterState { + final String error; + + const WaterHeaterFailedState({required this.error}); + + @override + List get props => [error]; +} + +final class WaterHeaterBatchFailedState extends WaterHeaterState { + final String error; + + const WaterHeaterBatchFailedState({required this.error}); + + @override + List get props => [error]; +} + +final class WaterHeaterLoadingState extends WaterHeaterState {} diff --git a/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart b/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart new file mode 100644 index 00000000..b4a02a02 --- /dev/null +++ b/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart @@ -0,0 +1,83 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +enum ScheduleModes { countdown, schedule, circulate, inching } + +class WaterHeaterStatusModel { + final String uuid; + final bool heaterSwitch; + final String scheduleModeString; + final int countdownHours; + final int countdownMinutes; + final ScheduleModes scheduleMode; + + WaterHeaterStatusModel({ + required this.uuid, + required this.heaterSwitch, + required this.scheduleModeString, + required this.countdownHours, + required this.countdownMinutes, + }) : scheduleMode = getScheduleMode(scheduleModeString); + + factory WaterHeaterStatusModel.fromJson(String id, List jsonList) { + late bool heaterSwitch; + late String scheduleMode; + late int countdownHours; + late int countdownMinutes; + + for (var status in jsonList) { + switch (status.code) { + case 'switch': + heaterSwitch = status.value ?? false; + break; + case 'schedule_mode': + scheduleMode = status.value ?? 'countdown'; + break; + case 'countdown_hours': + countdownHours = status.value ?? 0; + break; + case 'countdown_minutes': + countdownMinutes = status.value ?? 0; + break; + } + } + + return WaterHeaterStatusModel( + uuid: id, + heaterSwitch: heaterSwitch, + scheduleModeString: scheduleMode, + countdownHours: countdownHours, + countdownMinutes: countdownMinutes, + ); + } + + WaterHeaterStatusModel copyWith({ + String? uuid, + bool? heaterSwitch, + String? scheduleModeString, + int? countdownHours, + int? countdownMinutes, + }) { + return WaterHeaterStatusModel( + uuid: uuid ?? this.uuid, + heaterSwitch: heaterSwitch ?? this.heaterSwitch, + scheduleModeString: scheduleModeString ?? this.scheduleModeString, + countdownHours: countdownHours ?? this.countdownHours, + countdownMinutes: countdownMinutes ?? this.countdownMinutes, + ); + } + + static ScheduleModes getScheduleMode(String value) { + switch (value) { + case 'countdown': + return ScheduleModes.countdown; + case 'schedule': + return ScheduleModes.schedule; + case 'circulate': + return ScheduleModes.circulate; + case 'inching': + return ScheduleModes.inching; + default: + return ScheduleModes.countdown; + } + } +} diff --git a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart new file mode 100644 index 00000000..715854ad --- /dev/null +++ b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.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/water_heater_status_model.dart'; +import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class WaterHeaterDeviceControl extends StatelessWidget + with HelperResponsiveLayout { + const WaterHeaterDeviceControl({super.key, required this.device}); + + final AllDevicesModel device; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => + WaterHeaterBloc()..add(WaterHeaterFetchStatusEvent(device.uuid!)), + child: BlocBuilder( + builder: (context, state) { + if (state is WaterHeaterLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is WaterHeaterDeviceStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is WaterHeaterFailedState || + state is WaterHeaterBatchFailedState) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + )); + } + + Widget _buildStatusControls( + BuildContext context, WaterHeaterStatusModel 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, + ), + children: [ + const SizedBox(), + ToggleWidget( + deviceId: device.uuid!, + code: 'water_heater', + value: false, + label: 'Water Heater', + onChange: (value) { + context.read().add(ToggleWaterHeaterEvent( + deviceId: device.uuid!, + isOn: value, + )); + }, + ), + ], + ); + } +} From e577cd32795e519d31efcc484d18a5e245f3dc4c Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 17 Sep 2024 01:07:26 +0300 Subject: [PATCH 11/65] schedual asset --- assets/images/scheduling.svg | 28 +++++++++ .../view/water_heater_device_control.dart | 47 ++++++++++++--- lib/utils/constants/assets.dart | 59 +++++++++++++------ 3 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 assets/images/scheduling.svg diff --git a/assets/images/scheduling.svg b/assets/images/scheduling.svg new file mode 100644 index 00000000..3492a0ea --- /dev/null +++ b/assets/images/scheduling.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart index 715854ad..75a697e3 100644 --- a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart +++ b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart @@ -1,10 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.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/water_heater_status_model.dart'; -import 'package:syncrow_web/pages/visitor_password/model/device_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'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class WaterHeaterDeviceControl extends StatelessWidget @@ -40,21 +44,16 @@ class WaterHeaterDeviceControl extends StatelessWidget final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); return GridView( - padding: const EdgeInsets.symmetric(horizontal: 50), + padding: const EdgeInsets.symmetric(horizontal: 150), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: isLarge || isExtraLarge - ? 3 - : isMedium - ? 2 - : 1, + crossAxisCount: isLarge || isExtraLarge || isMedium ? 2 : 1, mainAxisExtent: 140, crossAxisSpacing: 12, mainAxisSpacing: 12, ), children: [ - const SizedBox(), ToggleWidget( deviceId: device.uuid!, code: 'water_heater', @@ -67,6 +66,38 @@ class WaterHeaterDeviceControl extends StatelessWidget )); }, ), + DeviceControlsContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 60, + height: 60, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, + ), + margin: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.all(12), + child: ClipOval( + child: SvgPicture.asset( + Assets.scheduling, + fit: BoxFit.fill, + ), + ), + ), + const Spacer(), + Text( + 'Scheduling', + textAlign: TextAlign.center, + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + ), + ), + ], + ), + ) ], ); } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 53fbb476..da578100 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -13,10 +13,12 @@ class Assets { static const String rightLine = "assets/images/right_line.png"; static const String google = "assets/images/google.svg"; static const String facebook = "assets/images/facebook.svg"; - static const String invisiblePassword = "assets/images/Password_invisible.svg"; + static const String invisiblePassword = + "assets/images/Password_invisible.svg"; static const String visiblePassword = "assets/images/password_visible.svg"; static const String accessIcon = "assets/images/access_icon.svg"; - static const String spaseManagementIcon = "assets/images/spase_management_icon.svg"; + static const String spaseManagementIcon = + "assets/images/spase_management_icon.svg"; static const String devicesIcon = "assets/images/devices_icon.svg"; static const String moveinIcon = "assets/images/movein_icon.svg"; static const String constructionIcon = "assets/images/construction_icon.svg"; @@ -29,13 +31,15 @@ class Assets { static const String emptyTable = "assets/images/empty_table.svg"; // General assets - static const String motionlessDetection = "assets/icons/motionless_detection.svg"; + static const String motionlessDetection = + "assets/icons/motionless_detection.svg"; static const String acHeating = "assets/icons/ac_heating.svg"; static const String acPowerOff = "assets/icons/ac_power_off.svg"; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg"; static const String resetOff = "assets/icons/reset_off.svg"; - static const String sensitivityOperationIcon = "assets/icons/sesitivity_operation_icon.svg"; + static const String sensitivityOperationIcon = + "assets/icons/sesitivity_operation_icon.svg"; static const String motionDetection = "assets/icons/motion_detection.svg"; static const String freezing = "assets/icons/freezing.svg"; static const String indicator = "assets/icons/indicator.svg"; @@ -56,7 +60,8 @@ class Assets { static const String celsiusDegrees = "assets/icons/celsius_degrees.svg"; static const String masterState = "assets/icons/master_state.svg"; static const String acPower = "assets/icons/ac_power.svg"; - static const String farDetectionFunction = "assets/icons/far_detection_function.svg"; + static const String farDetectionFunction = + "assets/icons/far_detection_function.svg"; static const String nobodyTime = "assets/icons/nobody_time.svg"; // Automation functions @@ -64,33 +69,47 @@ class Assets { "assets/icons/automation_functions/temp_password_unlock.svg"; static const String doorlockNormalOpen = "assets/icons/automation_functions/doorlock_normal_open.svg"; - static const String doorbell = "assets/icons/automation_functions/doorbell.svg"; + static const String doorbell = + "assets/icons/automation_functions/doorbell.svg"; static const String remoteUnlockViaApp = "assets/icons/automation_functions/remote_unlock_via_app.svg"; - static const String doubleLock = "assets/icons/automation_functions/double_lock.svg"; - static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg"; - static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg"; - static const String presenceState = "assets/icons/automation_functions/presence_state.svg"; - static const String currentTemp = "assets/icons/automation_functions/current_temp.svg"; - static const String presence = "assets/icons/automation_functions/presence.svg"; + static const String doubleLock = + "assets/icons/automation_functions/double_lock.svg"; + static const String selfTestResult = + "assets/icons/automation_functions/self_test_result.svg"; + static const String lockAlarm = + "assets/icons/automation_functions/lock_alarm.svg"; + static const String presenceState = + "assets/icons/automation_functions/presence_state.svg"; + static const String currentTemp = + "assets/icons/automation_functions/current_temp.svg"; + static const String presence = + "assets/icons/automation_functions/presence.svg"; static const String residualElectricity = "assets/icons/automation_functions/residual_electricity.svg"; - static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg"; - static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg"; + static const String hijackAlarm = + "assets/icons/automation_functions/hijack_alarm.svg"; + static const String passwordUnlock = + "assets/icons/automation_functions/password_unlock.svg"; static const String remoteUnlockRequest = "assets/icons/automation_functions/remote_unlock_req.svg"; - static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg"; + static const String cardUnlock = + "assets/icons/automation_functions/card_unlock.svg"; static const String motion = "assets/icons/automation_functions/motion.svg"; static const String fingerprintUnlock = "assets/icons/automation_functions/fingerprint_unlock.svg"; // Presence Sensor Assets static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; - static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg"; + static const String sensorPresenceIcon = + "assets/icons/sensor_presence_ic.svg"; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; - static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg"; - static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg"; - static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg"; + static const String illuminanceRecordIcon = + "assets/icons/illuminance_record_ic.svg"; + static const String presenceRecordIcon = + "assets/icons/presence_record_ic.svg"; + static const String helpDescriptionIcon = + "assets/icons/help_description_ic.svg"; static const String lightPulp = "assets/icons/light_pulb.svg"; static const String acDevice = "assets/icons/ac_device.svg"; @@ -127,4 +146,6 @@ class Assets { static const String curtainIcon = "assets/images/curtain.svg"; static const String unlock = 'assets/icons/unlock_ic.svg'; static const String firmware = 'assets/icons/firmware.svg'; + //assets/images/scheduling.svg + static const String scheduling = 'assets/images/scheduling.svg'; } From 3c8d3feba341d481eea4c83aab5e540a9465b043 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 17 Sep 2024 01:28:35 +0300 Subject: [PATCH 12/65] start working on scheduling --- .../water_heater/bloc/water_heater_bloc.dart | 15 +- .../water_heater/bloc/water_heater_event.dart | 11 +- .../water_heater/bloc/water_heater_state.dart | 19 +- .../view/water_heater_device_control.dart | 240 ++++++++++++++++-- 4 files changed, 252 insertions(+), 33 deletions(-) diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index 47632653..01507097 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -12,6 +12,7 @@ class WaterHeaterBloc extends Bloc { WaterHeaterBloc() : super(WaterHeaterInitial()) { on(_fetchWaterHeaterStatus); on(_controlWaterHeater); + on(_showScheduleView); on(_updateScheduleEvent); on(_stopScheduleEvent); } @@ -23,14 +24,24 @@ class WaterHeaterBloc extends Bloc { UpdateScheduleEvent event, Emitter emit, ) { - emit(WaterHeaterScheduleState( - scheduleType: event.scheduleType, + emit(WaterHeaterScheduleViewState( + scheduleMode: event.scheduleMode, hours: event.hours, minutes: event.minutes, isActive: true, )); } + FutureOr _showScheduleView( + ShowScheduleViewEvent event, Emitter emit) { + emit(const WaterHeaterScheduleViewState( + scheduleMode: ScheduleModes.countdown, + hours: 6, + minutes: 23, + isActive: false, + )); + } + FutureOr _stopScheduleEvent( StopScheduleEvent event, Emitter emit, diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index 3adf23dd..2f508a35 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -18,14 +18,15 @@ final class ToggleWaterHeaterEvent extends WaterHeaterEvent { } final class UpdateScheduleEvent extends WaterHeaterEvent { - final ScheduleType scheduleType; + final ScheduleModes scheduleMode; final int hours; final int minutes; - const UpdateScheduleEvent(this.scheduleType, this.hours, this.minutes); + const UpdateScheduleEvent( + {required this.scheduleMode, required this.hours, required this.minutes}); @override - List get props => [scheduleType, hours, minutes]; + List get props => [scheduleMode, hours, minutes]; } final class StopScheduleEvent extends WaterHeaterEvent {} @@ -47,3 +48,7 @@ class WaterHeaterFetchBatchStatusEvent extends WaterHeaterEvent { @override List get props => [deviceId]; } + +class ShowScheduleViewEvent extends WaterHeaterEvent { + const ShowScheduleViewEvent(); +} diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart index bc9ac051..349be2be 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart @@ -21,7 +21,7 @@ final class WaterHeaterToggleState extends WaterHeaterState { } final class WaterHeaterScheduleState extends WaterHeaterState { - final ScheduleType scheduleType; + final ScheduleModes scheduleType; final int hours; final int minutes; final bool isActive; @@ -74,3 +74,20 @@ final class WaterHeaterBatchFailedState extends WaterHeaterState { } final class WaterHeaterLoadingState extends WaterHeaterState {} + +class WaterHeaterScheduleViewState extends WaterHeaterState { + final ScheduleModes scheduleMode; + final int hours; + final int minutes; + final bool isActive; + + const WaterHeaterScheduleViewState({ + required this.scheduleMode, + required this.hours, + required this.minutes, + required this.isActive, + }); + + @override + List get props => [scheduleMode, hours, minutes]; +} diff --git a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart index 75a697e3..2d975370 100644 --- a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart +++ b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart @@ -28,6 +28,8 @@ class WaterHeaterDeviceControl extends StatelessWidget return const Center(child: CircularProgressIndicator()); } else if (state is WaterHeaterDeviceStatusLoaded) { return _buildStatusControls(context, state.status); + } else if (state is WaterHeaterScheduleViewState) { + return _buildScheduleView(context, state); } else if (state is WaterHeaterFailedState || state is WaterHeaterBatchFailedState) { return const Center(child: Text('Error fetching status')); @@ -66,39 +68,223 @@ class WaterHeaterDeviceControl extends StatelessWidget )); }, ), - DeviceControlsContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - width: 60, - height: 60, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: ColorsManager.whiteColors, - ), - margin: const EdgeInsets.symmetric(horizontal: 4), - padding: const EdgeInsets.all(12), - child: ClipOval( - child: SvgPicture.asset( - Assets.scheduling, - fit: BoxFit.fill, + GestureDetector( + onTap: () { + context.read().add(const ShowScheduleViewEvent()); + }, + child: DeviceControlsContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 60, + height: 60, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, + ), + margin: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.all(12), + child: ClipOval( + child: SvgPicture.asset( + Assets.scheduling, + fit: BoxFit.fill, + ), ), ), - ), - const Spacer(), - Text( - 'Scheduling', - textAlign: TextAlign.center, - style: context.textTheme.titleMedium!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor, + const Spacer(), + Text( + 'Scheduling', + textAlign: TextAlign.center, + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + ), ), - ), - ], + ], + ), ), ) ], ); } + + Widget _buildScheduleView( + BuildContext context, WaterHeaterScheduleViewState state) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Scheduling', + style: context.textTheme.titleLarge!.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.dialogBlueTitle, + ), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: ListTile( + title: const Text('Countdown'), + leading: Radio( + value: ScheduleModes.countdown, + groupValue: state.scheduleMode, + onChanged: (ScheduleModes? value) { + if (value != null) { + context.read().add(UpdateScheduleEvent( + scheduleMode: value, + hours: state.hours, + minutes: state.minutes, + )); + } + }, + ), + ), + ), + Expanded( + child: ListTile( + title: const Text('Schedule'), + leading: Radio( + value: ScheduleModes.schedule, + groupValue: state.scheduleMode, + onChanged: (ScheduleModes? value) { + if (value != null) { + context.read().add(UpdateScheduleEvent( + scheduleMode: value, + hours: state.hours, + minutes: state.minutes, + )); + } + }, + ), + ), + ), + Expanded( + child: ListTile( + title: const Text('Circulate'), + leading: Radio( + value: ScheduleModes.circulate, + groupValue: state.scheduleMode, + onChanged: (ScheduleModes? value) { + if (value != null) { + context.read().add(UpdateScheduleEvent( + scheduleMode: value, + hours: state.hours, + minutes: state.minutes, + )); + } + }, + ), + ), + ), + Expanded( + child: ListTile( + title: const Text('Inching'), + leading: Radio( + value: ScheduleModes.inching, + groupValue: state.scheduleMode, + onChanged: (ScheduleModes? value) { + if (value != null) { + context.read().add(UpdateScheduleEvent( + scheduleMode: value, + hours: state.hours, + minutes: state.minutes, + )); + } + }, + ), + ), + ), + ], + ), + const SizedBox(height: 20), + if (state.scheduleMode == ScheduleModes.countdown || + state.scheduleMode == ScheduleModes.inching) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Hours input + _buildTimeInputField( + label: 'h', + initialValue: state.hours.toString(), + onChanged: (value) { + int hours = int.tryParse(value) ?? 0; + context.read().add(UpdateScheduleEvent( + scheduleMode: state.scheduleMode, + hours: hours, + minutes: state.minutes, + )); + }, + ), + const SizedBox(width: 10), + // Minutes input + _buildTimeInputField( + label: 'm', + initialValue: state.minutes.toString(), + onChanged: (value) { + int minutes = int.tryParse(value) ?? 0; + context.read().add(UpdateScheduleEvent( + scheduleMode: state.scheduleMode, + hours: state.hours, + minutes: minutes, + )); + }, + ), + ], + ), + ], + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + Navigator.of(context) + .pop(); // Close the dialog or scheduling view + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey[400], + ), + child: const Text('Cancel'), + ), + const SizedBox(width: 20), + ElevatedButton( + onPressed: () { + // Handle saving schedule logic + }, + child: const Text('Save'), + ), + ], + ), + ], + ), + ); + } + + Widget _buildTimeInputField({ + required String label, + required String initialValue, + required Function(String) onChanged, + }) { + return Column( + children: [ + Text(label, style: const TextStyle(fontSize: 18)), + SizedBox( + width: 50, + child: TextField( + keyboardType: TextInputType.number, + textAlign: TextAlign.center, + decoration: const InputDecoration(border: UnderlineInputBorder()), + onChanged: onChanged, + controller: TextEditingController(text: initialValue), + ), + ), + ], + ); + } } From db7a072ecf29674616857fc31d2b75314c6da181 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 17 Sep 2024 23:41:22 +0300 Subject: [PATCH 13/65] wokring on connecting batch api --- .../bloc/device_managment_bloc.dart | 7 ---- .../all_devices/models/fake_door_sensor.dart | 33 ------------------- .../models/fake_heater_device.dart | 33 ------------------- .../view/device_managment_page.dart | 3 ++ 4 files changed, 3 insertions(+), 73 deletions(-) delete mode 100644 lib/pages/device_managment/all_devices/models/fake_door_sensor.dart delete mode 100644 lib/pages/device_managment/all_devices/models/fake_heater_device.dart diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart index c3104c65..d57f4685 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart @@ -1,10 +1,8 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/models/fake_door_sensor.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; -import '../models/fake_heater_device.dart'; part 'device_managment_event.dart'; part 'device_managment_state.dart'; @@ -34,11 +32,6 @@ class DeviceManagementBloc try { final devices = await DevicesManagementApi().fetchDevices(); _selectedDevices.clear(); - - /// add fake device for heater - devices.insert(0, fakeWaterHeaterDevice); - devices.insert(1, fakeMainDoorSensor); - //// _devices = devices; _filteredDevices = devices; _calculateDeviceCounts(); diff --git a/lib/pages/device_managment/all_devices/models/fake_door_sensor.dart b/lib/pages/device_managment/all_devices/models/fake_door_sensor.dart deleted file mode 100644 index 6682de18..00000000 --- a/lib/pages/device_managment/all_devices/models/fake_door_sensor.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; - -AllDevicesModel fakeMainDoorSensor = AllDevicesModel( - room: DevicesModelRoom( - uuid: "12de8f60-7104-4726-b5f8-ea426c0c7c3d", name: "Main Hall"), - unit: DevicesModelUnit( - uuid: "08fd3dcf-d13a-40db-970d-d0ce893df30e", name: "Entrance Unit 1"), - productUuid: "fake-uuid-main-door-sensor", - productType: "DS", - permissionType: "CONTROLLABLE", - activeTime: 1722178888, - category: "sensor", - categoryName: "Door Sensor", - createTime: 1722178888, - gatewayId: "b49df7395gfd8c19047krmk", - icon: "smart/icon/bay1642572935122vdsS/2b2f5fffaa5bbf81c3164fc313df2023.png", - ip: "", - lat: "31.92", - localKey: "A/43+:7M", - lon: "35.85", - model: "D03ZLSDSA2", - name: "Main Door Sensor", - nodeId: "70a523ffece8a7f9", - online: true, - ownerId: "199300932", - sub: true, - timeZone: "+03:00", - updateTime: 1723627123, - uuid: "9c32dac5-ce0c-4c85-b45c-8e16511174cg", - batteryLevel: 85, -); diff --git a/lib/pages/device_managment/all_devices/models/fake_heater_device.dart b/lib/pages/device_managment/all_devices/models/fake_heater_device.dart deleted file mode 100644 index 8ce1bb90..00000000 --- a/lib/pages/device_managment/all_devices/models/fake_heater_device.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; -import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; - -AllDevicesModel fakeWaterHeaterDevice = AllDevicesModel( - room: DevicesModelRoom( - uuid: "75ea7d60-5104-4726-b5f8-ea426c0c6a1b", name: "Kitchen"), - unit: DevicesModelUnit( - uuid: "04fd1dcf-f24a-40db-970d-d0be884ed30f", name: "Flat 101"), - productUuid: "fake-uuid-kitchen-water-heater", - productType: "WH", - permissionType: "CONTROLLABLE", - activeTime: 1722173778, - category: "kg", - categoryName: "Water Heater", - createTime: 1722173778, - gatewayId: "bf0294123ed2c19067skrk", - icon: "smart/icon/bay1642572935385vcsA/2b1f5efbaa5bbf81c3164fa312cf2032.png", - ip: "", - lat: "31.97", - localKey: "T/39+:9M", - lon: "35.89", - model: "S01ZLSWBSA3", - name: "Kitchen Water Heater", - nodeId: "60a423fffed5a7f6", - online: true, - ownerId: "199200732", - sub: true, - timeZone: "+03:00", - updateTime: 1723626515, - uuid: "5b31dae4-ce9c-4c70-b52b-7e150654sdf56", - batteryLevel: null, -); diff --git a/lib/pages/device_managment/all_devices/view/device_managment_page.dart b/lib/pages/device_managment/all_devices/view/device_managment_page.dart index 93480ae5..643c58f4 100644 --- a/lib/pages/device_managment/all_devices/view/device_managment_page.dart +++ b/lib/pages/device_managment/all_devices/view/device_managment_page.dart @@ -42,3 +42,6 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { ); } } + + + From 7d5b5340db565c659b5bf47c230abb6914074197 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 18 Sep 2024 00:30:03 +0300 Subject: [PATCH 14/65] push water heater single control --- .../bloc/two_gang_switch_bloc.dart | 11 +-- .../water_heater/bloc/water_heater_bloc.dart | 91 +++++++++++-------- .../water_heater/bloc/water_heater_event.dart | 8 +- .../water_heater/bloc/water_heater_state.dart | 37 +------- .../models/water_heater_status_model.dart | 48 ++++++---- .../view/water_heater_device_control.dart | 9 +- 6 files changed, 97 insertions(+), 107 deletions(-) diff --git a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart index 98ce8071..7db9d56c 100644 --- a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart +++ b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart @@ -8,8 +8,7 @@ import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_ga import 'package:syncrow_web/services/devices_mang_api.dart'; class TwoGangSwitchBloc extends Bloc { - TwoGangSwitchBloc({required this.deviceId}) - : super(TwoGangSwitchInitial()) { + TwoGangSwitchBloc({required this.deviceId}) : super(TwoGangSwitchInitial()) { on(_onFetchDeviceStatus); on(_onControl); on(_onFetchBatchStatus); @@ -89,7 +88,6 @@ class TwoGangSwitchBloc extends Bloc { if (code == 'switch_2') { deviceStatus = deviceStatus.copyWith(switch2: value); } - } bool _getValueByCode(String code) { @@ -103,14 +101,13 @@ class TwoGangSwitchBloc extends Bloc { } } - Future _onFetchBatchStatus( TwoGangSwitchFetchBatchEvent event, - Emitter< TwoGangSwitchState> emit) async { + Future _onFetchBatchStatus(TwoGangSwitchFetchBatchEvent event, + Emitter emit) async { emit(TwoGangSwitchLoading()); try { final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = - TwoGangStatusModel.fromJson(event.deviceId, status.status); + deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status); emit(TwoGangSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(TwoGangSwitchError(e.toString())); diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index 01507097..43372b2d 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -1,9 +1,9 @@ import 'dart:async'; - import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.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'; part 'water_heater_event.dart'; part 'water_heater_state.dart'; @@ -46,10 +46,10 @@ class WaterHeaterBloc extends Bloc { StopScheduleEvent event, Emitter emit, ) { - if (state is WaterHeaterScheduleState) { - final currentState = state as WaterHeaterScheduleState; - emit(WaterHeaterScheduleState( - scheduleType: currentState.scheduleType, + if (state is WaterHeaterScheduleViewState) { + final currentState = state as WaterHeaterScheduleViewState; + emit(WaterHeaterScheduleViewState( + scheduleMode: currentState.scheduleMode, hours: currentState.hours, minutes: currentState.minutes, isActive: false, @@ -59,15 +59,16 @@ class WaterHeaterBloc extends Bloc { FutureOr _controlWaterHeater( ToggleWaterHeaterEvent event, Emitter emit) async { - final oldValue = deviceStatus.heaterSwitch; + final oldValue = _getValueByCode(event.code); - _updateLocalValue(event.isOn); + _updateLocalValue(event.code, event.value, emit); - emit(WaterHeaterToggleState(isOn: event.isOn)); + emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); await _runDebounce( deviceId: event.deviceId, - value: event.isOn, + code: event.code, + value: event.value, oldValue: oldValue, emit: emit, ); @@ -75,8 +76,9 @@ class WaterHeaterBloc extends Bloc { Future _runDebounce({ required String deviceId, - required bool value, - required bool oldValue, + required String code, + required dynamic value, + required dynamic oldValue, required Emitter emit, }) async { if (_timer != null) { @@ -85,28 +87,47 @@ class WaterHeaterBloc extends Bloc { _timer = Timer(const Duration(milliseconds: 500), () async { try { - //// TODO: implement control + final status = await DevicesManagementApi().deviceControl( + deviceId, + Status(code: code, value: value), + ); - // final status = await DevicesManagementApi().deviceControl( - // deviceId, Status(value: value, code: 'heaterSwitch')); - - // if (!status) { - // _revertValueAndEmit(deviceId, oldValue, emit); - // } + if (!status) { + _revertValueAndEmit(deviceId, code, oldValue, emit); + } } catch (e) { - _revertValueAndEmit(deviceId, oldValue, emit); + _revertValueAndEmit(deviceId, code, oldValue, emit); } }); } - void _revertValueAndEmit( - String deviceId, bool oldValue, Emitter emit) { - _updateLocalValue(oldValue); - emit(WaterHeaterToggleState(isOn: oldValue)); + void _revertValueAndEmit(String deviceId, String code, dynamic oldValue, + Emitter emit) { + _updateLocalValue(code, oldValue, emit); + emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); } - void _updateLocalValue(bool value) { - deviceStatus = deviceStatus.copyWith(heaterSwitch: value); + void _updateLocalValue( + String code, dynamic value, Emitter emit) { + switch (code) { + case 'switch_1': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(heaterSwitch: value); + } + break; + default: + break; + } + } + + dynamic _getValueByCode(String code) { + switch (code) { + case 'switch_1': + return deviceStatus.heaterSwitch; + + default: + return null; + } } FutureOr _fetchWaterHeaterStatus( @@ -114,22 +135,12 @@ class WaterHeaterBloc extends Bloc { emit(WaterHeaterLoadingState()); try { - // final status = - // await DevicesManagementApi().getDeviceStatus(event.deviceId); - // deviceStatus = - // WaterHeaterStatusModel.fromJson(event.deviceId, status.status); + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + WaterHeaterStatusModel.fromJson(event.deviceId, status.status); - final List fakeStatusList = [ - Status(code: 'switch', value: true), - Status(code: 'schedule_mode', value: 'countdown'), - Status(code: 'countdown_hours', value: 6), - Status(code: 'countdown_minutes', value: 23), - ]; - - final fakeWaterHeaterStatus = - WaterHeaterStatusModel.fromJson(event.deviceId, fakeStatusList); - - emit(WaterHeaterDeviceStatusLoaded(fakeWaterHeaterStatus)); + emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); } catch (e) { emit(WaterHeaterFailedState(error: e.toString())); } diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index 2f508a35..4c1354f9 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -8,13 +8,15 @@ sealed class WaterHeaterEvent extends Equatable { } final class ToggleWaterHeaterEvent extends WaterHeaterEvent { - final bool isOn; + final dynamic value; final String deviceId; + final String code; - const ToggleWaterHeaterEvent({required this.isOn, required this.deviceId}); + const ToggleWaterHeaterEvent( + {required this.value, required this.deviceId, required this.code}); @override - List get props => [isOn]; + List get props => [value]; } final class UpdateScheduleEvent extends WaterHeaterEvent { diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart index 349be2be..a4216e44 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart @@ -11,31 +11,7 @@ sealed class WaterHeaterState extends Equatable { final class WaterHeaterInitial extends WaterHeaterState {} -final class WaterHeaterToggleState extends WaterHeaterState { - final bool isOn; - - const WaterHeaterToggleState({required this.isOn}); - - @override - List get props => [isOn]; -} - -final class WaterHeaterScheduleState extends WaterHeaterState { - final ScheduleModes scheduleType; - final int hours; - final int minutes; - final bool isActive; - - const WaterHeaterScheduleState({ - required this.scheduleType, - required this.hours, - required this.minutes, - required this.isActive, - }); - - @override - List get props => [scheduleType, hours, minutes, isActive]; -} +final class WaterHeaterLoadingState extends WaterHeaterState {} final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { final WaterHeaterStatusModel status; @@ -46,15 +22,6 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { List get props => [status]; } -final class WaterHeaterBatchStatusLoaded extends WaterHeaterState { - final WaterHeaterStatusModel status; - - const WaterHeaterBatchStatusLoaded(this.status); - - @override - List get props => [status]; -} - final class WaterHeaterFailedState extends WaterHeaterState { final String error; @@ -73,8 +40,6 @@ final class WaterHeaterBatchFailedState extends WaterHeaterState { List get props => [error]; } -final class WaterHeaterLoadingState extends WaterHeaterState {} - class WaterHeaterScheduleViewState extends WaterHeaterState { final ScheduleModes scheduleMode; final int hours; diff --git a/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart b/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart index b4a02a02..3cf29a96 100644 --- a/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart +++ b/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart @@ -5,38 +5,46 @@ enum ScheduleModes { countdown, schedule, circulate, inching } class WaterHeaterStatusModel { final String uuid; final bool heaterSwitch; - final String scheduleModeString; final int countdownHours; final int countdownMinutes; final ScheduleModes scheduleMode; + final String relayStatus; + final String cycleTiming; WaterHeaterStatusModel({ required this.uuid, required this.heaterSwitch, - required this.scheduleModeString, required this.countdownHours, required this.countdownMinutes, - }) : scheduleMode = getScheduleMode(scheduleModeString); + required this.relayStatus, + required this.cycleTiming, + required this.scheduleMode, + }); factory WaterHeaterStatusModel.fromJson(String id, List jsonList) { - late bool heaterSwitch; - late String scheduleMode; - late int countdownHours; - late int countdownMinutes; + late bool heaterSwitch = false; + late int countdownHours = 0; + late int countdownMinutes = 0; + late String relayStatus = ''; + late String cycleTiming = ''; + late ScheduleModes scheduleMode = ScheduleModes.countdown; for (var status in jsonList) { switch (status.code) { - case 'switch': + case 'switch_1': heaterSwitch = status.value ?? false; break; - case 'schedule_mode': - scheduleMode = status.value ?? 'countdown'; - break; - case 'countdown_hours': + case 'countdown_1': countdownHours = status.value ?? 0; break; - case 'countdown_minutes': - countdownMinutes = status.value ?? 0; + case 'relay_status': + relayStatus = status.value ?? 'memory'; + break; + case 'cycle_timing': + cycleTiming = status.value ?? ''; + break; + case 'switch_inching': + scheduleMode = getScheduleMode(status.value ?? 'countdown'); break; } } @@ -44,25 +52,31 @@ class WaterHeaterStatusModel { return WaterHeaterStatusModel( uuid: id, heaterSwitch: heaterSwitch, - scheduleModeString: scheduleMode, countdownHours: countdownHours, countdownMinutes: countdownMinutes, + relayStatus: relayStatus, + cycleTiming: cycleTiming, + scheduleMode: scheduleMode, ); } WaterHeaterStatusModel copyWith({ String? uuid, bool? heaterSwitch, - String? scheduleModeString, int? countdownHours, int? countdownMinutes, + String? relayStatus, + String? cycleTiming, + ScheduleModes? scheduleMode, }) { return WaterHeaterStatusModel( uuid: uuid ?? this.uuid, heaterSwitch: heaterSwitch ?? this.heaterSwitch, - scheduleModeString: scheduleModeString ?? this.scheduleModeString, countdownHours: countdownHours ?? this.countdownHours, countdownMinutes: countdownMinutes ?? this.countdownMinutes, + relayStatus: relayStatus ?? this.relayStatus, + cycleTiming: cycleTiming ?? this.cycleTiming, + scheduleMode: scheduleMode ?? this.scheduleMode, ); } diff --git a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart index 2d975370..b1cc82d5 100644 --- a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart +++ b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart @@ -58,19 +58,20 @@ class WaterHeaterDeviceControl extends StatelessWidget children: [ ToggleWidget( deviceId: device.uuid!, - code: 'water_heater', - value: false, + code: 'switch_1', + value: status.heaterSwitch, label: 'Water Heater', onChange: (value) { context.read().add(ToggleWaterHeaterEvent( deviceId: device.uuid!, - isOn: value, + code: 'switch_1', + value: value, )); }, ), GestureDetector( onTap: () { - context.read().add(const ShowScheduleViewEvent()); + // context.read().add(const ShowScheduleViewEvent()); }, child: DeviceControlsContainer( child: Column( From 9876ff2e039fd448b1a705be65828b05f043081c Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 18 Sep 2024 01:36:17 +0300 Subject: [PATCH 15/65] push main door device design --- assets/icons/main_door.svg | 23 +++ assets/icons/main_door_notifi.svg | 10 ++ assets/icons/main_door_reports.svg | 18 +++ .../helper/route_controls_based_code.dart | 7 +- .../bloc/main_door_sensor_bloc.dart | 139 ++++++++++++++++ .../bloc/main_door_sensor_event.dart | 55 +++++++ .../bloc/main_door_sensor_state.dart | 68 ++++++++ .../models/main_door_status_model.dart | 47 ++++++ .../view/main_door_control_view.dart | 148 ++++++++++++++++++ lib/utils/constants/assets.dart | 6 + 10 files changed, 520 insertions(+), 1 deletion(-) create mode 100644 assets/icons/main_door.svg create mode 100644 assets/icons/main_door_notifi.svg create mode 100644 assets/icons/main_door_reports.svg create mode 100644 lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart create mode 100644 lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart create mode 100644 lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart create mode 100644 lib/pages/device_managment/main_door_sensor/models/main_door_status_model.dart create mode 100644 lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart diff --git a/assets/icons/main_door.svg b/assets/icons/main_door.svg new file mode 100644 index 00000000..5f378012 --- /dev/null +++ b/assets/icons/main_door.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/main_door_notifi.svg b/assets/icons/main_door_notifi.svg new file mode 100644 index 00000000..34d44e1c --- /dev/null +++ b/assets/icons/main_door_notifi.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/main_door_reports.svg b/assets/icons/main_door_reports.svg new file mode 100644 index 00000000..f0eb413a --- /dev/null +++ b/assets/icons/main_door_reports.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 2872ee6d..5065ec31 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batc import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart'; @@ -55,7 +56,11 @@ mixin RouteControlsBasedCode { case 'AC': return AcDeviceControlsView(device: device); case 'WH': - return WaterHeaterDeviceControl(device: device,); + return WaterHeaterDeviceControl( + device: device, + ); + case 'DS': + return MainDoorSensorControlView(device: device); default: return const SizedBox(); } diff --git a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart new file mode 100644 index 00000000..bb72f1d3 --- /dev/null +++ b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart @@ -0,0 +1,139 @@ +import 'dart:async'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +class MainDoorSensorBloc + extends Bloc { + MainDoorSensorBloc() : super(MainDoorSensorInitial()) { + on(_onFetchDeviceStatus); + on(_onControl); + on(_onFetchBatchStatus); + on(_fetchReports); + } + + late MainDoorSensorStatusModel deviceStatus; + Timer? _timer; + + FutureOr _onFetchDeviceStatus(MainDoorSensorFetchDeviceEvent event, + Emitter emit) async { + emit(MainDoorSensorLoadingState()); + try { + final status = await DevicesManagementApi() + .getDeviceStatus(event.deviceId) + .then((value) => value.status); + + deviceStatus = MainDoorSensorStatusModel.fromJson(event.deviceId, status); + emit(MainDoorSensorDeviceStatusLoaded(deviceStatus)); + } catch (e) { + emit(MainDoorSensorFailedState(error: e.toString())); + } + } + + FutureOr _onControl( + MainDoorSensorControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + + emit(MainDoorSensorDeviceStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + ); + } + + Future _runDebounce({ + required String deviceId, + required String code, + required bool value, + required bool oldValue, + required Emitter emit, + }) async { + if (_timer != null) { + _timer!.cancel(); + } + + _timer = Timer(const Duration(milliseconds: 500), () async { + try { + final status = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + + if (!status) { + _revertValueAndEmit(deviceId, code, oldValue, emit); + } + + emit(MainDoorSensorDeviceStatusLoaded(deviceStatus)); + } catch (e) { + emit(MainDoorSensorFailedState(error: e.toString())); + } + }); + } + + void _revertValueAndEmit(String deviceId, String code, bool oldValue, + Emitter emit) { + _updateLocalValue(code, oldValue); + emit(MainDoorSensorDeviceStatusLoaded(deviceStatus)); + } + + void _updateLocalValue(String code, bool value) { + switch (code) { + case 'doorcontact_state': + deviceStatus = deviceStatus.copyWith(doorContactState: value); + break; + default: + break; + } + } + + // Retrieve the current value by code + bool _getValueByCode(String code) { + switch (code) { + case 'doorcontact_state': + return deviceStatus.doorContactState; + default: + return false; + } + } + + // Fetch batch status for multiple devices (if needed) + FutureOr _onFetchBatchStatus(MainDoorSensorFetchBatchEvent event, + Emitter emit) async { + emit(MainDoorSensorLoadingState()); + try { + // final batchStatus = + // await DevicesManagementApi().getBatchDeviceStatus(event.deviceIds); + // Assuming you need to update multiple devices status here + // You might need a list or map of MainDoorSensorStatusModel for batch processing + // emit(MainDoorSensorBatchStatusLoaded(batchStatus)); + } catch (e) { + emit(MainDoorSensorBatchFailedState(error: e.toString())); + } + } + + // Fetch reports related to the main door sensor + FutureOr _fetchReports(MainDoorSensorReportsEvent event, + Emitter emit) async { + emit(MainDoorSensorLoadingState()); + try { + final reports = await DevicesManagementApi.getDeviceReports( + event.deviceId, event.code); + emit(MainDoorSensorReportLoaded(reports)); + } catch (e) { + emit(MainDoorSensorFailedState(error: e.toString())); + } + } + + @override + Future close() { + _timer?.cancel(); + return super.close(); + } +} diff --git a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart new file mode 100644 index 00000000..4db304b0 --- /dev/null +++ b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart @@ -0,0 +1,55 @@ +import 'package:equatable/equatable.dart'; + +class MainDoorSensorEvent extends Equatable { + @override + List get props => []; +} + +class MainDoorSensorFetchDeviceEvent extends MainDoorSensorEvent { + final String deviceId; + + MainDoorSensorFetchDeviceEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class MainDoorSensorFetchBatchEvent extends MainDoorSensorEvent { + final String deviceId; + + MainDoorSensorFetchBatchEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class MainDoorSensorControl extends MainDoorSensorEvent { + final String deviceId; + final String code; + final bool value; + + MainDoorSensorControl( + {required this.deviceId, required this.code, required this.value}); + + @override + List get props => [deviceId, code, value]; +} + +class MainDoorSensorBatchControl extends MainDoorSensorEvent { + final List deviceId; + final String code; + final bool value; + + MainDoorSensorBatchControl( + {required this.deviceId, required this.code, required this.value}); + + @override + List get props => [deviceId, code, value]; +} + +class MainDoorSensorReportsEvent extends MainDoorSensorEvent { + final String deviceId; + final String code; + + MainDoorSensorReportsEvent({required this.deviceId, required this.code}); +} diff --git a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart new file mode 100644 index 00000000..d482245b --- /dev/null +++ b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart @@ -0,0 +1,68 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart'; + +class MainDoorSensorState extends Equatable { + @override + List get props => []; +} + +class MainDoorSensorInitial extends MainDoorSensorState {} + +class MainDoorSensorLoadingState extends MainDoorSensorState {} + +class MainDoorSensorDeviceStatusLoaded extends MainDoorSensorState { + final MainDoorSensorStatusModel status; + + MainDoorSensorDeviceStatusLoaded(this.status); + + @override + List get props => [status]; +} + +class MainDoorSensorFailedState extends MainDoorSensorState { + final String error; + + MainDoorSensorFailedState({required this.error}); + + @override + List get props => [error]; +} + +class MainDoorSensorBatchFailedState extends MainDoorSensorState { + final String error; + + MainDoorSensorBatchFailedState({required this.error}); + + @override + List get props => [error]; +} + +class MainDoorSensorBatchStatusLoaded extends MainDoorSensorState { + final List status; + + MainDoorSensorBatchStatusLoaded(this.status); + + @override + List get props => [status]; +} + +class MainDoorSensorReportLoaded extends MainDoorSensorState { + final DeviceReport deviceReport; + + MainDoorSensorReportLoaded(this.deviceReport); + + @override + List get props => [deviceReport]; +} + +class MainDoorSensorReportsLoadingState extends MainDoorSensorState {} + +class MainDoorSensorReportsFailedState extends MainDoorSensorState { + final String error; + + MainDoorSensorReportsFailedState({required this.error}); + + @override + List get props => [error]; +} diff --git a/lib/pages/device_managment/main_door_sensor/models/main_door_status_model.dart b/lib/pages/device_managment/main_door_sensor/models/main_door_status_model.dart new file mode 100644 index 00000000..52dda7a3 --- /dev/null +++ b/lib/pages/device_managment/main_door_sensor/models/main_door_status_model.dart @@ -0,0 +1,47 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class MainDoorSensorStatusModel { + final String uuid; + final bool doorContactState; + final int batteryPercentage; + + MainDoorSensorStatusModel({ + required this.uuid, + required this.doorContactState, + required this.batteryPercentage, + }); + + factory MainDoorSensorStatusModel.fromJson(String id, List jsonList) { + late bool doorContactState = false; + late int batteryPercentage = 0; + + for (var status in jsonList) { + switch (status.code) { + case 'doorcontact_state': + doorContactState = status.value ?? false; + break; + case 'battery_percentage': + batteryPercentage = status.value ?? 0; + break; + } + } + + return MainDoorSensorStatusModel( + uuid: id, + doorContactState: doorContactState, + batteryPercentage: batteryPercentage, + ); + } + + MainDoorSensorStatusModel copyWith({ + String? uuid, + bool? doorContactState, + int? batteryPercentage, + }) { + return MainDoorSensorStatusModel( + uuid: uuid ?? this.uuid, + doorContactState: doorContactState ?? this.doorContactState, + batteryPercentage: batteryPercentage ?? this.batteryPercentage, + ); + } +} diff --git a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart new file mode 100644 index 00000000..a4ac0777 --- /dev/null +++ b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.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'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class MainDoorSensorControlView extends StatelessWidget + with HelperResponsiveLayout { + const MainDoorSensorControlView({super.key, required this.device}); + + final AllDevicesModel device; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => MainDoorSensorBloc() + ..add(MainDoorSensorFetchDeviceEvent(device.uuid!)), + child: BlocBuilder( + builder: (context, state) { + if (state is MainDoorSensorLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is MainDoorSensorDeviceStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is MainDoorSensorFailedState || + state is MainDoorSensorBatchFailedState) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + )); + } + + Widget _buildStatusControls( + BuildContext context, MainDoorSensorStatusModel 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, + ), + children: [ + IconNameStatusContainer( + name: 'Open', + icon: Assets.mainDoor, + onTap: () {}, + status: status.doorContactState, + textColor: ColorsManager.red, + paddingAmount: 8, + ), + IconNameStatusContainer( + name: 'Open/Close\n Record', + icon: Assets.mainDoorReports, + onTap: () {}, + status: false, + textColor: ColorsManager.blackColor, + ), + IconNameStatusContainer( + name: 'Notifications\n Settings', + icon: Assets.mainDoorNotifi, + onTap: () {}, + status: false, + textColor: ColorsManager.blackColor, + ), + ], + ); + } +} + +class IconNameStatusContainer extends StatelessWidget { + const IconNameStatusContainer({ + super.key, + required this.name, + required this.icon, + required this.onTap, + required this.status, + required this.textColor, + this.paddingAmount = 12, + }); + + final String name; + final String icon; + final GestureTapCallback onTap; + final bool status; + final Color textColor; + final double? paddingAmount; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: DeviceControlsContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 60, + height: 60, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, + ), + margin: const EdgeInsets.symmetric(horizontal: 4), + padding: EdgeInsets.all(paddingAmount ?? 12), + child: ClipOval( + child: SvgPicture.asset( + icon, + fit: BoxFit.fill, + ), + ), + ), + const Spacer(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: Text( + name, + textAlign: TextAlign.start, + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: textColor, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index da578100..3c22e031 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -148,4 +148,10 @@ class Assets { static const String firmware = 'assets/icons/firmware.svg'; //assets/images/scheduling.svg static const String scheduling = 'assets/images/scheduling.svg'; + //assets/icons/main_door_notifi.svg + static const String mainDoorNotifi = 'assets/icons/main_door_notifi.svg'; + //assets/icons/main_door_reports.svg + static const String mainDoorReports = 'assets/icons/main_door_reports.svg'; + //assets/icons/main_door.svg + static const String mainDoor = 'assets/icons/main_door.svg'; } From 7c28012d799ebbb4f67ce97c9cc41c26d5cb9ee3 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 18 Sep 2024 02:23:42 +0300 Subject: [PATCH 16/65] connect reports to main door --- .../all_devices/models/device_reports.dart | 14 ++++--- .../bloc/main_door_sensor_bloc.dart | 17 ++++---- .../bloc/main_door_sensor_event.dart | 10 ++++- .../view/main_door_control_view.dart | 41 +++++++++++++++++-- .../shared/table/report_table.dart | 24 ++++++++--- lib/services/devices_mang_api.dart | 12 ++++-- lib/utils/constants/api_const.dart | 8 +++- 7 files changed, 98 insertions(+), 28 deletions(-) diff --git a/lib/pages/device_managment/all_devices/models/device_reports.dart b/lib/pages/device_managment/all_devices/models/device_reports.dart index 05604b25..82c49386 100644 --- a/lib/pages/device_managment/all_devices/models/device_reports.dart +++ b/lib/pages/device_managment/all_devices/models/device_reports.dart @@ -13,11 +13,15 @@ class DeviceReport { DeviceReport.fromJson(Map json) : deviceUuid = json['deviceUuid'] as String?, - startTime = json['startTime'] as int?, - endTime = json['endTime'] as int?, - data = (json['data'] as List?) - ?.map((e) => DeviceEvent.fromJson(e as Map)) - .toList(); + startTime = int.tryParse(json['startTime'].toString()) ?? + json['startTime'] as int?, + endTime = + int.tryParse(json['endTime'].toString()) ?? json['endTime'] as int?, + data = json['data'] != null + ? (json['data'] as List?) + ?.map((e) => DeviceEvent.fromJson(e as Map)) + .toList() + : []; Map toJson() => { 'deviceUuid': deviceUuid, diff --git a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart index bb72f1d3..50281c98 100644 --- a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart +++ b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart'; @@ -63,16 +65,17 @@ class MainDoorSensorBloc _timer = Timer(const Duration(milliseconds: 500), () async { try { - final status = await DevicesManagementApi() + final response = await DevicesManagementApi() .deviceControl(deviceId, Status(code: code, value: value)); - if (!status) { + if (!response) { _revertValueAndEmit(deviceId, code, oldValue, emit); } - - emit(MainDoorSensorDeviceStatusLoaded(deviceStatus)); } catch (e) { - emit(MainDoorSensorFailedState(error: e.toString())); + if (e is DioException && e.response != null) { + debugPrint('Error response: ${e.response?.data}'); + } + _revertValueAndEmit(deviceId, code, oldValue, emit); } }); } @@ -93,7 +96,6 @@ class MainDoorSensorBloc } } - // Retrieve the current value by code bool _getValueByCode(String code) { switch (code) { case 'doorcontact_state': @@ -118,13 +120,12 @@ class MainDoorSensorBloc } } - // Fetch reports related to the main door sensor FutureOr _fetchReports(MainDoorSensorReportsEvent event, Emitter emit) async { emit(MainDoorSensorLoadingState()); try { final reports = await DevicesManagementApi.getDeviceReports( - event.deviceId, event.code); + event.deviceId, event.code, event.from, event.to); emit(MainDoorSensorReportLoaded(reports)); } catch (e) { emit(MainDoorSensorFailedState(error: e.toString())); diff --git a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart index 4db304b0..40bec02c 100644 --- a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart +++ b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart @@ -50,6 +50,14 @@ class MainDoorSensorBatchControl extends MainDoorSensorEvent { class MainDoorSensorReportsEvent extends MainDoorSensorEvent { final String deviceId; final String code; + final String from; + final String to; + @override + List get props => [deviceId, code, from, to]; - MainDoorSensorReportsEvent({required this.deviceId, required this.code}); + MainDoorSensorReportsEvent( + {required this.deviceId, + required this.code, + required this.from, + required this.to}); } diff --git a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart index a4ac0777..3cc3e82e 100644 --- a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart +++ b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart @@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_do import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/pages/device_managment/shared/table/report_table.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'; @@ -25,10 +26,23 @@ class MainDoorSensorControlView extends StatelessWidget ..add(MainDoorSensorFetchDeviceEvent(device.uuid!)), child: BlocBuilder( builder: (context, state) { - if (state is MainDoorSensorLoadingState) { + if (state is MainDoorSensorLoadingState || + state is MainDoorSensorReportsLoadingState) { return const Center(child: CircularProgressIndicator()); } else if (state is MainDoorSensorDeviceStatusLoaded) { return _buildStatusControls(context, state.status); + } else if (state is MainDoorSensorReportLoaded) { + return ReportsTable( + report: state.deviceReport, + onRowTap: (index) {}, + onClose: () { + context + .read() + .add(MainDoorSensorFetchDeviceEvent(device.uuid!)); + }, + hideValueShowDescription: true, + mainDoorSensor: true, + ); } else if (state is MainDoorSensorFailedState || state is MainDoorSensorBatchFailedState) { return const Center(child: Text('Error fetching status')); @@ -60,9 +74,15 @@ class MainDoorSensorControlView extends StatelessWidget ), children: [ IconNameStatusContainer( - name: 'Open', + name: status.doorContactState ? 'Open' : 'Close', icon: Assets.mainDoor, - onTap: () {}, + onTap: () { + context.read().add(MainDoorSensorControl( + deviceId: device.uuid!, + code: 'doorcontact_state', + value: !status.doorContactState, + )); + }, status: status.doorContactState, textColor: ColorsManager.red, paddingAmount: 8, @@ -70,7 +90,20 @@ class MainDoorSensorControlView extends StatelessWidget IconNameStatusContainer( name: 'Open/Close\n Record', icon: Assets.mainDoorReports, - onTap: () {}, + onTap: () { + final from = DateTime.now() + .subtract(const Duration(days: 30)) + .millisecondsSinceEpoch; + final to = DateTime.now().millisecondsSinceEpoch; + context.read().add( + MainDoorSensorReportsEvent( + deviceId: device.uuid!, + code: 'doorcontact_state', + from: from.toString(), + to: to.toString(), + ), + ); + }, status: false, textColor: ColorsManager.blackColor, ), diff --git a/lib/pages/device_managment/shared/table/report_table.dart b/lib/pages/device_managment/shared/table/report_table.dart index d655964b..e46242a3 100644 --- a/lib/pages/device_managment/shared/table/report_table.dart +++ b/lib/pages/device_managment/shared/table/report_table.dart @@ -10,14 +10,18 @@ class ReportsTable extends StatelessWidget { final String? thirdColumnDescription; final Function(int index) onRowTap; final VoidCallback onClose; + bool? hideValueShowDescription; + bool? mainDoorSensor; - const ReportsTable({ + ReportsTable({ super.key, required this.report, required this.onRowTap, required this.onClose, this.thirdColumnTitle, this.thirdColumnDescription, + this.hideValueShowDescription, + this.mainDoorSensor, }); @override @@ -57,10 +61,20 @@ class ReportsTable extends StatelessWidget { children: [ TableCellWidget(value: date), TableCellWidget(value: time), - TableCellWidget( - value: '${data.value!} ${thirdColumnDescription ?? ''}', - onTap: () => onRowTap(index), - ), + hideValueShowDescription == true + ? TableCellWidget( + value: mainDoorSensor == true + ? data.value == 'true' + ? 'Open' + : 'Close' + : thirdColumnDescription ?? '', + onTap: () => onRowTap(index), + ) + : TableCellWidget( + value: + '${data.value!} ${thirdColumnDescription ?? ''}', + onTap: () => onRowTap(index), + ), ], ); }), diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 70ce96f1..b9362550 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -65,7 +65,8 @@ class DevicesManagementApi { } } - static Future> getDevicesByGatewayId(String gatewayId) async { + static Future> getDevicesByGatewayId( + String gatewayId) async { final response = await HTTPService().get( path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId), showServerMessage: false, @@ -94,9 +95,14 @@ class DevicesManagementApi { return response; } - static Future getDeviceReports(String uuid, String code) async { + static Future getDeviceReports(String uuid, String code, + [String? from, String? to]) async { final response = await HTTPService().get( - path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code), + path: ApiEndpoints.getDeviceLogsByDate + .replaceAll('{uuid}', uuid) + .replaceAll('{code}', code) + .replaceAll('{startTime}', from ?? '') + .replaceAll('{endTime}', to ?? ''), showServerMessage: false, expectedResponseModel: (json) { return DeviceReport.fromJson(json); diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 5d24b501..894da828 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -11,12 +11,14 @@ abstract class ApiEndpoints { static const String visitorPassword = '/visitor-password'; static const String getDevices = '/visitor-password/devices'; - static const String sendOnlineOneTime = '/visitor-password/temporary-password/online/one-time'; + static const String sendOnlineOneTime = + '/visitor-password/temporary-password/online/one-time'; static const String sendOnlineMultipleTime = '/visitor-password/temporary-password/online/multiple-time'; //offline Password - static const String sendOffLineOneTime = '/visitor-password/temporary-password/offline/one-time'; + static const String sendOffLineOneTime = + '/visitor-password/temporary-password/offline/one-time'; static const String sendOffLineMultipleTime = '/visitor-password/temporary-password/offline/multiple-time'; @@ -32,4 +34,6 @@ abstract class ApiEndpoints { static const String openDoorLock = '/door-lock/open/{doorLockUuid}'; static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}'; + static const String getDeviceLogsByDate = + '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; } From abb0a58468bf6cddb2b977cc006a30d72f33699b Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 18 Sep 2024 12:27:00 +0300 Subject: [PATCH 17/65] push ac batch control --- .../device_managment/ac/bloc/ac_bloc.dart | 65 +++++++-- .../device_managment/ac/bloc/ac_event.dart | 31 +++- .../device_managment/ac/bloc/ac_state.dart | 10 ++ .../device_managment/ac/model/ac_model.dart | 8 +- .../ac/view/ac_device_batch_control.dart | 51 ++++--- .../ac/view/ac_device_control.dart | 2 +- .../batch_control_list/batch_ac_mode.dart | 82 +++++++++++ .../batch_current_temp.dart | 138 ++++++++++++++++++ .../batch_control_list/batch_fan_speed.dart | 91 ++++++++++++ .../ac/view/control_list/ac_mode.dart | 2 +- .../ac/view/control_list/ac_toggle.dart | 2 +- .../ac/view/control_list/current_temp.dart | 2 +- .../ac/view/control_list/fan_speed.dart | 2 +- .../bloc/main_door_sensor_bloc.dart | 2 +- .../view/main_door_control_view.dart | 8 +- .../view/wall_light_batch_control.dart | 2 +- .../view/wall_light_device_control.dart | 2 +- .../shared/table/report_table.dart | 3 +- .../toggle_widget.dart} | 0 .../view/living_room_batch_controls.dart | 2 +- .../view/living_room_device_control.dart | 2 +- .../view/wall_light_batch_control.dart | 2 +- .../view/wall_light_device_control.dart | 2 +- .../view/water_heater_device_control.dart | 2 +- lib/services/devices_mang_api.dart | 67 ++++++++- lib/utils/constants/api_const.dart | 2 + 26 files changed, 516 insertions(+), 66 deletions(-) create mode 100644 lib/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart create mode 100644 lib/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart create mode 100644 lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart rename lib/pages/device_managment/{three_gang_switch/widgets/living_toggle_widget.dart => shared/toggle_widget.dart} (100%) diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart index ab9cc265..54f36889 100644 --- a/lib/pages/device_managment/ac/bloc/ac_bloc.dart +++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart @@ -14,13 +14,14 @@ class AcBloc extends Bloc { Timer? _timer; AcBloc({required this.deviceId}) : super(AcsInitialState()) { - on(_onFetchAcStatus); - on(_onFetchAcBatchStatus); - on(_onAcControl); + on(_onFetchAcStatus); + on(_onFetchAcBatchStatus); + on(_onAcControl); + on(_onAcBatchControl); } FutureOr _onFetchAcStatus( - AcFetchDeviceStatus event, Emitter emit) async { + AcFetchDeviceStatusEvent event, Emitter emit) async { emit(AcsLoadingState()); try { final status = @@ -32,7 +33,8 @@ class AcBloc extends Bloc { } } - FutureOr _onAcControl(AcControl event, Emitter emit) async { + FutureOr _onAcControl( + AcControlEvent event, Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value, emit); @@ -40,6 +42,7 @@ class AcBloc extends Bloc { emit(ACStatusLoaded(deviceStatus)); await _runDebounce( + isBatch: false, deviceId: event.deviceId, code: event.code, value: event.value, @@ -49,27 +52,43 @@ class AcBloc extends Bloc { } Future _runDebounce({ - required String deviceId, + required dynamic deviceId, required String code, required dynamic value, required dynamic oldValue, required Emitter emit, + required bool isBatch, }) async { + late String id; + + if (deviceId is List) { + id = deviceId.first; + } else { + id = deviceId; + } + if (_timer != null) { _timer!.cancel(); } _timer = Timer(const Duration(seconds: 1), () async { try { - final response = await DevicesManagementApi() - .deviceControl(deviceId, Status(code: code, value: value)); + late bool response; + if (isBatch) { + response = await DevicesManagementApi() + .deviceBatchControl(deviceId, code, value); + } else { + response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + } + if (!response) { - _revertValueAndEmit(deviceId, code, oldValue, emit); + _revertValueAndEmit(id, code, oldValue, emit); } } catch (e) { if (e is DioException && e.response != null) { debugPrint('Error response: ${e.response?.data}'); } - _revertValueAndEmit(deviceId, code, oldValue, emit); + _revertValueAndEmit(id, code, oldValue, emit); } }); } @@ -78,7 +97,6 @@ class AcBloc extends Bloc { String deviceId, String code, dynamic oldValue, Emitter emit) { _updateLocalValue(code, oldValue, emit); emit(ACStatusLoaded(deviceStatus)); - emit(const AcsFailedState(error: 'Failed to control the device.')); } void _updateLocalValue(String code, dynamic value, Emitter emit) { @@ -136,15 +154,34 @@ class AcBloc extends Bloc { } FutureOr _onFetchAcBatchStatus( - AcFetchBatchStatus event, Emitter emit) async { + AcFetchBatchStatusEvent event, Emitter emit) async { emit(AcsLoadingState()); try { final status = - await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); + await DevicesManagementApi().getBatchStatus(event.devicesIds); + deviceStatus = + AcStatusModel.fromJson(event.devicesIds.first, status.status); emit(ACStatusLoaded(deviceStatus)); } catch (e) { emit(AcsFailedState(error: e.toString())); } } + + FutureOr _onAcBatchControl( + AcBatchControlEvent event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value, emit); + + emit(ACStatusLoaded(deviceStatus)); + + await _runDebounce( + isBatch: true, + deviceId: event.devicesIds, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + ); + } } diff --git a/lib/pages/device_managment/ac/bloc/ac_event.dart b/lib/pages/device_managment/ac/bloc/ac_event.dart index ab743b47..acb81d95 100644 --- a/lib/pages/device_managment/ac/bloc/ac_event.dart +++ b/lib/pages/device_managment/ac/bloc/ac_event.dart @@ -7,30 +7,30 @@ sealed class AcsEvent extends Equatable { List get props => []; } -class AcFetchDeviceStatus extends AcsEvent { +class AcFetchDeviceStatusEvent extends AcsEvent { final String deviceId; - const AcFetchDeviceStatus(this.deviceId); + const AcFetchDeviceStatusEvent(this.deviceId); @override List get props => [deviceId]; } -class AcFetchBatchStatus extends AcsEvent { - final String deviceId; +class AcFetchBatchStatusEvent extends AcsEvent { + final List devicesIds; - const AcFetchBatchStatus(this.deviceId); + const AcFetchBatchStatusEvent(this.devicesIds); @override - List get props => [deviceId]; + List get props => [devicesIds]; } -class AcControl extends AcsEvent { +class AcControlEvent extends AcsEvent { final String deviceId; final String code; final dynamic value; - const AcControl({ + const AcControlEvent({ required this.deviceId, required this.code, required this.value, @@ -39,3 +39,18 @@ class AcControl extends AcsEvent { @override List get props => [deviceId, code, value]; } + +class AcBatchControlEvent extends AcsEvent { + final List devicesIds; + final String code; + final dynamic value; + + const AcBatchControlEvent({ + required this.devicesIds, + required this.code, + required this.value, + }); + + @override + List get props => [devicesIds, code, value]; +} diff --git a/lib/pages/device_managment/ac/bloc/ac_state.dart b/lib/pages/device_managment/ac/bloc/ac_state.dart index 9a3b07f9..dfd12e6d 100644 --- a/lib/pages/device_managment/ac/bloc/ac_state.dart +++ b/lib/pages/device_managment/ac/bloc/ac_state.dart @@ -22,6 +22,16 @@ class ACStatusLoaded extends AcsState { List get props => [status, timestamp]; } +class AcBatchStatusLoaded extends AcsState { + final AcStatusModel status; + final DateTime timestamp; + + AcBatchStatusLoaded(this.status) : timestamp = DateTime.now(); + + @override + List get props => [status, timestamp]; +} + class AcsFailedState extends AcsState { final String error; diff --git a/lib/pages/device_managment/ac/model/ac_model.dart b/lib/pages/device_managment/ac/model/ac_model.dart index 621b9326..2803e51e 100644 --- a/lib/pages/device_managment/ac/model/ac_model.dart +++ b/lib/pages/device_managment/ac/model/ac_model.dart @@ -40,16 +40,16 @@ class AcStatusModel { acSwitch = status.value ?? false; break; case 'mode': - mode = status.value ?? 'cold'; // default to 'cold' if null + mode = status.value ?? 'cold'; break; case 'temp_set': - tempSet = status.value ?? 210; // default value if null + tempSet = status.value ?? 210; break; case 'temp_current': - currentTemp = status.value ?? 210; // default value if null + currentTemp = status.value ?? 210; break; case 'level': - fanSpeeds = status.value ?? 'low'; // default value if null + fanSpeeds = status.value ?? 'low'; break; case 'child_lock': childLock = status.value ?? false; diff --git a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart index dc597090..edda3c5d 100644 --- a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart @@ -3,12 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart'; -import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_mode.dart'; -import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_toggle.dart'; -import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_temp.dart'; -import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart'; +import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart'; +import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart'; +import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; @@ -25,7 +25,7 @@ class AcDeviceBatchControlView extends StatelessWidget final isMedium = isMediumScreenSize(context); return BlocProvider( create: (context) => AcBloc(deviceId: devicesIds.first) - ..add(AcFetchBatchStatus(devicesIds.first)), + ..add(AcFetchBatchStatusEvent(devicesIds)), child: BlocBuilder( builder: (context, state) { if (state is ACStatusLoaded) { @@ -44,34 +44,49 @@ class AcDeviceBatchControlView extends StatelessWidget mainAxisSpacing: 12, ), children: [ - AcToggle( - value: state.status.acSwitch, - code: 'switch', + ToggleWidget( deviceId: devicesIds.first, + code: 'switch', + value: state.status.acSwitch, + label: 'ThermoState', + onChange: (value) { + context.read().add(AcBatchControlEvent( + devicesIds: devicesIds, + code: 'switch', + value: value, + )); + }, ), - CurrentTemp( + BatchCurrentTemp( currentTemp: state.status.currentTemp, tempSet: state.status.tempSet, code: 'temp_set', - deviceId: devicesIds.first, + devicesIds: devicesIds, ), - AcMode( + BatchAcMode( value: state.status.acMode, code: 'mode', - deviceId: devicesIds.first, + devicesIds: devicesIds, ), - FanSpeedControl( + BatchFanSpeedControl( value: state.status.acFanSpeed, code: 'level', - deviceId: devicesIds.first, + devicesIds: devicesIds, ), - AcToggle( - value: state.status.childLock, - code: 'child_lock', + ToggleWidget( deviceId: devicesIds.first, - description: 'Child Lock', + code: 'child_lock', + value: state.status.childLock, + label: 'Child Lock', icon: state.status.childLock ? Assets.childLock : Assets.unlock, + onChange: (value) { + context.read().add(AcBatchControlEvent( + devicesIds: devicesIds, + code: 'child_lock', + value: value, + )); + }, ), FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5), FactoryResetWidget(deviceId: devicesIds.first), diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index fdfa263c..7ae6f973 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -23,7 +23,7 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { final isMedium = isMediumScreenSize(context); return BlocProvider( create: (context) => AcBloc(deviceId: device.uuid!) - ..add(AcFetchDeviceStatus(device.uuid!)), + ..add(AcFetchDeviceStatusEvent(device.uuid!)), child: BlocBuilder( builder: (context, state) { if (state is ACStatusLoaded) { diff --git a/lib/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart b/lib/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart new file mode 100644 index 00000000..8b601ac2 --- /dev/null +++ b/lib/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; + +class BatchAcMode extends StatelessWidget { + const BatchAcMode({ + super.key, + required this.value, + required this.code, + required this.devicesIds, + }); + + final TempModes value; + final String code; + final List devicesIds; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: ColorsManager.greyColor.withOpacity(0.2), + border: Border.all(color: ColorsManager.boxDivider), + ), + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildIconContainer(context, TempModes.cold, Assets.freezing, + value == TempModes.cold), + _buildIconContainer( + context, TempModes.hot, Assets.acSun, value == TempModes.hot), + _buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, + value == TempModes.wind), + ], + ), + ); + } + + Widget _buildIconContainer( + BuildContext context, TempModes mode, String assetPath, bool isSelected) { + return Flexible( + child: GestureDetector( + onTap: () { + context.read().add( + AcBatchControlEvent( + devicesIds: devicesIds, + code: code, + value: mode.name, + ), + ); + }, + child: Container( + width: 50, + height: 50, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, + border: Border.all( + color: isSelected ? Colors.blue : Colors.transparent, + width: 2.0, + ), + ), + margin: const EdgeInsets.symmetric(horizontal: 4), + padding: const EdgeInsets.all(4), + child: ClipOval( + child: SvgPicture.asset( + assetPath, + fit: BoxFit.contain, + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart b/lib/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart new file mode 100644 index 00000000..e6d9378f --- /dev/null +++ b/lib/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart @@ -0,0 +1,138 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; +import 'package:syncrow_web/pages/device_managment/shared/celciuse_symbol.dart'; +import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; + +class BatchCurrentTemp extends StatefulWidget { + const BatchCurrentTemp({ + super.key, + required this.code, + required this.devicesIds, + required this.currentTemp, + required this.tempSet, + }); + + final String code; + final List devicesIds; + final int currentTemp; + final int tempSet; + + @override + State createState() => _CurrentTempState(); +} + +class _CurrentTempState extends State { + late double _adjustedValue; + Timer? _debounce; + + @override + void initState() { + super.initState(); + _adjustedValue = _initialAdjustedValue(widget.tempSet); + } + + double _initialAdjustedValue(dynamic value) { + if (value is int || value is double) { + double doubleValue = value.toDouble(); + return doubleValue > 99 ? doubleValue / 10 : doubleValue; + } else { + throw ArgumentError('Invalid value type: Expected int or double'); + } + } + + void _onValueChanged(double newValue) { + if (_debounce?.isActive ?? false) { + _debounce?.cancel(); + } + _debounce = Timer(const Duration(milliseconds: 500), () { + context.read().add( + AcBatchControlEvent( + devicesIds: widget.devicesIds, + code: widget.code, + value: (newValue * 10).toInt(), + ), + ); + }); + } + + @override + void dispose() { + _debounce?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: ColorsManager.greyColor.withOpacity(0.2), + border: Border.all(color: ColorsManager.boxDivider), + ), + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Current Temperature', + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.grey), + ), + const SizedBox( + height: 5, + ), + Row( + children: [ + Text( + (widget.currentTemp > 99 + ? widget.currentTemp / 10 + : widget.currentTemp) + .toString(), + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith(color: Colors.grey), + ), + const CelsiusSymbol( + color: Colors.grey, + ) + ], + ), + ], + ), + const Spacer(), + IncrementDecrementWidget( + value: _adjustedValue.toString(), + description: '°C', + descriptionColor: ColorsManager.dialogBlueTitle, + onIncrement: () { + if (_adjustedValue < 30) { + setState(() { + _adjustedValue++; + }); + _onValueChanged(_adjustedValue); + } + }, + onDecrement: () { + if (_adjustedValue > 20) { + setState(() { + _adjustedValue--; + }); + _onValueChanged(_adjustedValue); + } + }), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart b/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart new file mode 100644 index 00000000..b48d602c --- /dev/null +++ b/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; + +class BatchFanSpeedControl extends StatelessWidget { + const BatchFanSpeedControl({ + super.key, + required this.value, + required this.code, + required this.devicesIds, + }); + + final FanSpeeds value; + final String code; + final List devicesIds; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: ColorsManager.greyColor.withOpacity(0.2), + border: Border.all(color: ColorsManager.boxDivider), + ), + padding: const EdgeInsets.all(8), + child: Column( + children: [ + Wrap( + runSpacing: 8, + spacing: 8, + children: [ + _buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, + value == FanSpeeds.auto), + _buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, + value == FanSpeeds.low), + ], + ), + Wrap( + runSpacing: 8, + spacing: 8, + children: [ + _buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, + value == FanSpeeds.middle), + _buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, + value == FanSpeeds.high), + ], + ) + ], + ), + ); + } + + Widget _buildIconContainer(BuildContext context, FanSpeeds speed, + String assetPath, bool isSelected) { + return GestureDetector( + onTap: () { + context.read().add( + AcBatchControlEvent( + devicesIds: devicesIds, + code: code, + value: speed.name, + ), + ); + }, + child: Container( + width: 50, + height: 50, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, + border: Border.all( + color: isSelected ? Colors.blue : Colors.transparent, + width: 2.0, + ), + ), + padding: const EdgeInsets.all(8), + child: ClipOval( + child: SvgPicture.asset( + assetPath, + fit: BoxFit.contain, + ), + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/ac/view/control_list/ac_mode.dart b/lib/pages/device_managment/ac/view/control_list/ac_mode.dart index 0e0cd276..65c7ae0a 100644 --- a/lib/pages/device_managment/ac/view/control_list/ac_mode.dart +++ b/lib/pages/device_managment/ac/view/control_list/ac_mode.dart @@ -49,7 +49,7 @@ class AcMode extends StatelessWidget { child: GestureDetector( onTap: () { context.read().add( - AcControl( + AcControlEvent( deviceId: deviceId, code: code, value: mode.name, diff --git a/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart b/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart index 53f79761..b9bf6014 100644 --- a/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart +++ b/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart @@ -57,7 +57,7 @@ class AcToggle extends StatelessWidget { value: value, onChanged: (newValue) { context.read().add( - AcControl( + AcControlEvent( deviceId: deviceId, code: code, value: newValue, diff --git a/lib/pages/device_managment/ac/view/control_list/current_temp.dart b/lib/pages/device_managment/ac/view/control_list/current_temp.dart index 2cf35228..5ff6fea6 100644 --- a/lib/pages/device_managment/ac/view/control_list/current_temp.dart +++ b/lib/pages/device_managment/ac/view/control_list/current_temp.dart @@ -50,7 +50,7 @@ class _CurrentTempState extends State { } _debounce = Timer(const Duration(milliseconds: 500), () { context.read().add( - AcControl( + AcControlEvent( deviceId: widget.deviceId, code: widget.code, value: (newValue * 10).toInt(), diff --git a/lib/pages/device_managment/ac/view/control_list/fan_speed.dart b/lib/pages/device_managment/ac/view/control_list/fan_speed.dart index d8d61d6b..c1e95d53 100644 --- a/lib/pages/device_managment/ac/view/control_list/fan_speed.dart +++ b/lib/pages/device_managment/ac/view/control_list/fan_speed.dart @@ -60,7 +60,7 @@ class FanSpeedControl extends StatelessWidget { return GestureDetector( onTap: () { context.read().add( - AcControl( + AcControlEvent( deviceId: deviceId, code: code, value: speed.name, diff --git a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart index 50281c98..71a4b699 100644 --- a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart +++ b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart @@ -124,7 +124,7 @@ class MainDoorSensorBloc Emitter emit) async { emit(MainDoorSensorLoadingState()); try { - final reports = await DevicesManagementApi.getDeviceReports( + final reports = await DevicesManagementApi.getDeviceReportsByDate( event.deviceId, event.code, event.from, event.to); emit(MainDoorSensorReportLoaded(reports)); } catch (e) { diff --git a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart index 3cc3e82e..657ebfee 100644 --- a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart +++ b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart @@ -76,13 +76,7 @@ class MainDoorSensorControlView extends StatelessWidget IconNameStatusContainer( name: status.doorContactState ? 'Open' : 'Close', icon: Assets.mainDoor, - onTap: () { - context.read().add(MainDoorSensorControl( - deviceId: device.uuid!, - code: 'doorcontact_state', - value: !status.doorContactState, - )); - }, + onTap: () {}, status: status.doorContactState, textColor: ColorsManager.red, paddingAmount: 8, diff --git a/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart b/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart index 78de7658..ffdcfa85 100644 --- a/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart +++ b/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart @@ -6,7 +6,7 @@ import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_lig import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; -import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class WallLightBatchControlView extends StatelessWidget diff --git a/lib/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart b/lib/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart index 85a4221e..a9e6ebbb 100644 --- a/lib/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart +++ b/lib/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart @@ -4,7 +4,7 @@ import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_lig import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart'; -import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class WallLightDeviceControl extends StatelessWidget diff --git a/lib/pages/device_managment/shared/table/report_table.dart b/lib/pages/device_managment/shared/table/report_table.dart index e46242a3..3cddef37 100644 --- a/lib/pages/device_managment/shared/table/report_table.dart +++ b/lib/pages/device_managment/shared/table/report_table.dart @@ -63,7 +63,8 @@ class ReportsTable extends StatelessWidget { TableCellWidget(value: time), hideValueShowDescription == true ? TableCellWidget( - value: mainDoorSensor == true + value: (mainDoorSensor != null && + mainDoorSensor == true) ? data.value == 'true' ? 'Open' : 'Close' diff --git a/lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart b/lib/pages/device_managment/shared/toggle_widget.dart similarity index 100% rename from lib/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart rename to lib/pages/device_managment/shared/toggle_widget.dart diff --git a/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart index f70605b8..daff5e63 100644 --- a/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart +++ b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart @@ -4,7 +4,7 @@ import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_ import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart'; -import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class LivingRoomBatchControlsView extends StatelessWidget diff --git a/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart b/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart index 07332217..b8226f57 100644 --- a/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart +++ b/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart'; -import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class LivingRoomDeviceControlsView extends StatelessWidget diff --git a/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart b/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart index 4926911a..323bc3f8 100644 --- a/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart +++ b/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; -import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart'; diff --git a/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart b/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart index fa42d096..e72756eb 100644 --- a/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart +++ b/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart'; diff --git a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart index b1cc82d5..14193ee0 100644 --- a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart +++ b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; -import 'package:syncrow_web/pages/device_managment/three_gang_switch/widgets/living_toggle_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.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/water_heater_status_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index b9362550..03ddf6ca 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -65,6 +65,31 @@ class DevicesManagementApi { } } + Future deviceBatchControl( + List uuids, String code, dynamic value) async { + try { + final body = { + 'devicesUuid': uuids, + 'code': code, + 'value': value, + }; + + final response = await HTTPService().post( + path: ApiEndpoints.deviceBatchControl, + body: body, + showServerMessage: true, + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return false; + } + } + static Future> getDevicesByGatewayId( String gatewayId) async { final response = await HTTPService().get( @@ -95,7 +120,23 @@ class DevicesManagementApi { return response; } - static Future getDeviceReports(String uuid, String code, + static Future getDeviceReports( + String uuid, + String code, + ) async { + final response = await HTTPService().get( + path: ApiEndpoints.getDeviceLogs + .replaceAll('{uuid}', uuid) + .replaceAll('{code}', code), + showServerMessage: false, + expectedResponseModel: (json) { + return DeviceReport.fromJson(json); + }, + ); + return response; + } + + static Future getDeviceReportsByDate(String uuid, String code, [String? from, String? to]) async { final response = await HTTPService().get( path: ApiEndpoints.getDeviceLogsByDate @@ -110,4 +151,28 @@ class DevicesManagementApi { ); return response; } + + Future getBatchStatus(List uuids) async { + try { + final queryParameters = { + 'devicesUuid': uuids.join(','), + }; + final response = await HTTPService().get( + path: ApiEndpoints.getBatchStatus, + queryParameters: queryParameters, + showServerMessage: true, + expectedResponseModel: (json) { + return DeviceStatus.fromJson(json['status']); + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return DeviceStatus( + productUuid: '', + productType: '', + status: [], + ); + } + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 894da828..3138c502 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -28,8 +28,10 @@ abstract class ApiEndpoints { static const String getAllDevices = '/device'; static const String getDeviceStatus = '/device/{uuid}/functions/status'; + static const String getBatchStatus = '/device/status/batch'; static const String deviceControl = '/device/{uuid}/control'; + static const String deviceBatchControl = '/device/control/batch'; static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices'; static const String openDoorLock = '/door-lock/open/{doorLockUuid}'; From fd09db6835a60ccaef9d789b346d1da2895e020e Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 18 Sep 2024 13:15:43 +0300 Subject: [PATCH 18/65] push ceiling sensor batch control --- .../ceiling_sensor/bloc/bloc.dart | 68 ++++++++++++++++--- .../ceiling_sensor/bloc/event.dart | 32 ++++++++- .../ceiling_sensor/bloc/state.dart | 9 +++ .../model/ceiling_sensor_model.dart | 25 +++++++ .../view/ceiling_sensor_batch_control.dart | 18 ++--- .../view/ceiling_sensor_controls.dart | 2 +- lib/services/devices_mang_api.dart | 2 +- 7 files changed, 132 insertions(+), 24 deletions(-) diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart b/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart index 055c21b1..ba8e4114 100644 --- a/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart +++ b/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart @@ -14,8 +14,9 @@ class CeilingSensorBloc extends Bloc { CeilingSensorBloc({required this.deviceId}) : super(CeilingInitialState()) { on(_fetchCeilingSensorStatus); - on(_fetchCeilingSensorBatchControl); + on(_fetchCeilingSensorBatchControl); on(_changeValue); + on(_onBatchControl); on(_getDeviceReports); on(_showDescription); on(_backToGridView); @@ -25,7 +26,8 @@ class CeilingSensorBloc extends Bloc { CeilingInitialEvent event, Emitter emit) async { emit(CeilingLoadingInitialState()); try { - var response = await DevicesManagementApi().getDeviceStatus(deviceId); + var response = await DevicesManagementApi() + .getDeviceStatus(event.deviceId); deviceStatus = CeilingSensorModel.fromJson(response.status); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); // _listenToChanges(); @@ -68,34 +70,76 @@ class CeilingSensorBloc extends Bloc { } emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); await _runDeBouncer( - deviceId: deviceId, code: event.code, value: event.value, emit: emit); + deviceId: deviceId, + code: event.code, + value: event.value, + emit: emit, + isBatch: false, + ); + } + + Future _onBatchControl( + CeilingBatchControlEvent event, Emitter emit) async { + emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus)); + if (event.code == 'sensitivity') { + deviceStatus.sensitivity = event.value; + } else if (event.code == 'none_body_time') { + deviceStatus.noBodyTime = event.value; + } else if (event.code == 'moving_max_dis') { + deviceStatus.maxDistance = event.value; + } else if (event.code == 'scene') { + deviceStatus.spaceType = getSpaceType(event.value); + } + emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); + await _runDeBouncer( + deviceId: event.deviceIds, + code: event.code, + value: event.value, + emit: emit, + isBatch: true, + ); } _runDeBouncer({ - required String deviceId, + required dynamic deviceId, required String code, required dynamic value, required Emitter emit, + required bool isBatch, }) { + late String id; + + if (deviceId is List) { + id = deviceId.first; + } else { + id = deviceId; + } + if (_timer != null) { _timer!.cancel(); } _timer = Timer(const Duration(seconds: 1), () async { try { - final response = await DevicesManagementApi() - .deviceControl(deviceId, Status(code: code, value: value)); + late bool response; + if (isBatch) { + response = await DevicesManagementApi() + .deviceBatchControl(deviceId, code, value); + } else { + response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + } if (!response) { - add(CeilingInitialEvent()); + add(CeilingInitialEvent(id)); } if (response == true && code == 'scene') { emit(CeilingLoadingInitialState()); await Future.delayed(const Duration(seconds: 1)); - add(CeilingInitialEvent()); + add(CeilingInitialEvent(id)); } } catch (_) { await Future.delayed(const Duration(milliseconds: 500)); - add(CeilingInitialEvent()); + add(CeilingInitialEvent(id)); } }); } @@ -131,10 +175,12 @@ class CeilingSensorBloc extends Bloc { } FutureOr _fetchCeilingSensorBatchControl( - CeilingBatchControlEvent event, Emitter emit) async { + CeilingFetchDeviceStatusEvent event, + Emitter emit) async { emit(CeilingLoadingInitialState()); try { - var response = await DevicesManagementApi().getDeviceStatus(deviceId); + var response = + await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = CeilingSensorModel.fromJson(response.status); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); } catch (e) { diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/event.dart b/lib/pages/device_managment/ceiling_sensor/bloc/event.dart index d445578e..c1efa47c 100644 --- a/lib/pages/device_managment/ceiling_sensor/bloc/event.dart +++ b/lib/pages/device_managment/ceiling_sensor/bloc/event.dart @@ -7,9 +7,37 @@ abstract class CeilingSensorEvent extends Equatable { List get props => []; } -class CeilingInitialEvent extends CeilingSensorEvent {} +class CeilingInitialEvent extends CeilingSensorEvent { + final String deviceId; + const CeilingInitialEvent(this.deviceId); -class CeilingBatchControlEvent extends CeilingSensorEvent {} + @override + List get props => [deviceId]; +} + +class CeilingFetchDeviceStatusEvent extends CeilingSensorEvent { + final List devicesIds; + + const CeilingFetchDeviceStatusEvent(this.devicesIds); + + @override + List get props => [devicesIds]; +} + +class CeilingBatchControlEvent extends CeilingSensorEvent { + final List deviceIds; + final String code; + final dynamic value; + + const CeilingBatchControlEvent({ + required this.deviceIds, + required this.code, + required this.value, + }); + + @override + List get props => [deviceIds, code, value]; +} class CeilingChangeValueEvent extends CeilingSensorEvent { final dynamic value; diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/state.dart b/lib/pages/device_managment/ceiling_sensor/bloc/state.dart index 0bd7e4ed..d9144474 100644 --- a/lib/pages/device_managment/ceiling_sensor/bloc/state.dart +++ b/lib/pages/device_managment/ceiling_sensor/bloc/state.dart @@ -66,3 +66,12 @@ class ShowCeilingDescriptionState extends CeilingSensorState { @override List get props => [description]; } + +class CeilingBatchControlSuccessState extends CeilingSensorState { + final CeilingSensorModel ceilingSensorModel; + + const CeilingBatchControlSuccessState({required this.ceilingSensorModel}); + + @override + List get props => [ceilingSensorModel]; +} diff --git a/lib/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart b/lib/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart index a79cbc19..18149990 100644 --- a/lib/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart +++ b/lib/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; + import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; class CeilingSensorModel { @@ -91,6 +92,30 @@ class CeilingSensorModel { spaceType: _spaceType, ); } + + CeilingSensorModel copyWith({ + String? presenceState, + int? sensitivity, + String? checkingResult, + int? presenceRange, + int? sportsPara, + String? bodyMovement, + String? noBodyTime, + int? maxDistance, + SpaceTypes? spaceType, + }) { + return CeilingSensorModel( + presenceState: presenceState ?? this.presenceState, + sensitivity: sensitivity ?? this.sensitivity, + checkingResult: checkingResult ?? this.checkingResult, + presenceRange: presenceRange ?? this.presenceRange, + sportsPara: sportsPara ?? this.sportsPara, + bodyMovement: bodyMovement ?? this.bodyMovement, + noBodyTime: noBodyTime ?? this.noBodyTime, + maxDistance: maxDistance ?? this.maxDistance, + spaceType: spaceType ?? this.spaceType, + ); + } } enum SpaceTypes { diff --git a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart index 5511c0bc..f95852a5 100644 --- a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart +++ b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart @@ -24,7 +24,7 @@ class CeilingSensorBatchControlView extends StatelessWidget final isMedium = isMediumScreenSize(context); return BlocProvider( create: (context) => CeilingSensorBloc(deviceId: devicesIds.first) - ..add(CeilingBatchControlEvent()), + ..add(CeilingFetchDeviceStatusEvent(devicesIds)), child: BlocBuilder( builder: (context, state) { if (state is CeilingLoadingInitialState || @@ -33,10 +33,6 @@ class CeilingSensorBatchControlView extends StatelessWidget } else if (state is CeilingUpdateState) { return _buildGridView(context, state.ceilingSensorModel, isExtraLarge, isLarge, isMedium); - } else if (state is CeilingReportsFailedState) { - final model = context.read().deviceStatus; - return _buildGridView( - context, model, isExtraLarge, isLarge, isMedium); } return const Center(child: Text('Error fetching status')); }, @@ -65,7 +61,8 @@ class CeilingSensorBatchControlView extends StatelessWidget description: 'Space Type', value: model.spaceType, action: (String value) => context.read().add( - CeilingChangeValueEvent( + CeilingBatchControlEvent( + deviceIds: devicesIds, code: 'scene', value: value, ), @@ -79,7 +76,8 @@ class CeilingSensorBatchControlView extends StatelessWidget steps: 1, action: (int value) { context.read().add( - CeilingChangeValueEvent( + CeilingBatchControlEvent( + deviceIds: devicesIds, code: 'sensitivity', value: value, ), @@ -94,7 +92,8 @@ class CeilingSensorBatchControlView extends StatelessWidget steps: 50, description: 'm', action: (int value) => context.read().add( - CeilingChangeValueEvent( + CeilingBatchControlEvent( + deviceIds: devicesIds, code: 'moving_max_dis', value: value, ), @@ -105,7 +104,8 @@ class CeilingSensorBatchControlView extends StatelessWidget title: 'Nobody Time:', description: '', action: (String value) => context.read().add( - CeilingChangeValueEvent( + CeilingBatchControlEvent( + deviceIds: devicesIds, code: 'nobody_time', value: value, ), diff --git a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart index 1f4d58f9..e2048ab4 100644 --- a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart +++ b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart @@ -29,7 +29,7 @@ class CeilingSensorControlsView extends StatelessWidget final isMedium = isMediumScreenSize(context); return BlocProvider( create: (context) => CeilingSensorBloc(deviceId: device.uuid ?? '') - ..add(CeilingInitialEvent()), + ..add(CeilingInitialEvent(device.uuid ?? '')), child: BlocBuilder( builder: (context, state) { if (state is CeilingLoadingInitialState || diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 03ddf6ca..4be915a2 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -79,7 +79,7 @@ class DevicesManagementApi { body: body, showServerMessage: true, expectedResponseModel: (json) { - return json['success'] ?? false; + return (json['successResults'] as List).isNotEmpty ; }, ); From c354abbeca4d5f96b6030de9df7aa38550454064 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 18 Sep 2024 13:55:36 +0300 Subject: [PATCH 19/65] push wall sensor batch control --- .../ceiling_sensor/bloc/state.dart | 9 -- .../wall_sensor/bloc/bloc.dart | 82 ++++++++++++++----- .../wall_sensor/bloc/event.dart | 25 +++++- .../view/wall_sensor_batch_control.dart | 14 ++-- .../view/wall_sensor_conrtols.dart | 4 +- 5 files changed, 93 insertions(+), 41 deletions(-) diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/state.dart b/lib/pages/device_managment/ceiling_sensor/bloc/state.dart index d9144474..0bd7e4ed 100644 --- a/lib/pages/device_managment/ceiling_sensor/bloc/state.dart +++ b/lib/pages/device_managment/ceiling_sensor/bloc/state.dart @@ -66,12 +66,3 @@ class ShowCeilingDescriptionState extends CeilingSensorState { @override List get props => [description]; } - -class CeilingBatchControlSuccessState extends CeilingSensorState { - final CeilingSensorModel ceilingSensorModel; - - const CeilingBatchControlSuccessState({required this.ceilingSensorModel}); - - @override - List get props => [ceilingSensorModel]; -} diff --git a/lib/pages/device_managment/wall_sensor/bloc/bloc.dart b/lib/pages/device_managment/wall_sensor/bloc/bloc.dart index 86e1d504..cc96955a 100644 --- a/lib/pages/device_managment/wall_sensor/bloc/bloc.dart +++ b/lib/pages/device_managment/wall_sensor/bloc/bloc.dart @@ -12,16 +12,17 @@ class WallSensorBloc extends Bloc { Timer? _timer; WallSensorBloc({required this.deviceId}) : super(WallSensorInitialState()) { - on(_fetchWallSensorStatus); - on(_fetchWallSensorBatchControl); + on(_fetchWallSensorStatus); + on(_fetchWallSensorBatchControl); on(_changeValue); + on(_onBatchControl); on(_getDeviceReports); on(_showDescription); on(_backToGridView); } void _fetchWallSensorStatus( - WallSensorInitialEvent event, Emitter emit) async { + WallSensorFetchStatusEvent event, Emitter emit) async { emit(WallSensorLoadingInitialState()); try { var response = await DevicesManagementApi().getDeviceStatus(deviceId); @@ -34,6 +35,21 @@ class WallSensorBloc extends Bloc { } } + // Fetch batch status + FutureOr _fetchWallSensorBatchControl( + WallSensorFetchBatchStatusEvent event, + Emitter emit) async { + emit(WallSensorLoadingInitialState()); + try { + var response = + await DevicesManagementApi().getBatchStatus(event.devicesIds); + deviceStatus = WallSensorModel.fromJson(response.status); + emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); + } catch (e) { + emit(WallSensorFailedState(error: e.toString())); + } + } + // _listenToChanges() { // try { // DatabaseReference ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); @@ -67,28 +83,63 @@ class WallSensorBloc extends Bloc { } emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); await _runDeBouncer( - deviceId: deviceId, code: event.code, value: event.value); + deviceId: deviceId, + code: event.code, + value: event.value, + isBatch: false, + emit: emit, + ); + } + + Future _onBatchControl( + WallSensorBatchControlEvent event, Emitter emit) async { + emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus)); + if (event.code == 'far_detection') { + deviceStatus.farDetection = event.value; + } else if (event.code == 'motionless_sensitivity') { + deviceStatus.motionlessSensitivity = event.value; + } else if (event.code == 'motion_sensitivity_value') { + deviceStatus.motionSensitivity = event.value; + } else if (event.code == 'no_one_time') { + deviceStatus.noBodyTime = event.value; + } + emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); + await _runDeBouncer( + deviceId: event.deviceIds, + code: event.code, + value: event.value, + emit: emit, + isBatch: true, + ); } _runDeBouncer({ - required String deviceId, + required dynamic deviceId, required String code, required dynamic value, + required Emitter emit, + required bool isBatch, }) { if (_timer != null) { _timer!.cancel(); } _timer = Timer(const Duration(seconds: 1), () async { try { - final response = await DevicesManagementApi() - .deviceControl(deviceId, Status(code: code, value: value)); + late bool response; + if (isBatch) { + response = await DevicesManagementApi() + .deviceBatchControl(deviceId, code, value); + } else { + response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + } if (!response) { - add(WallSensorInitialEvent()); + add(WallSensorFetchStatusEvent()); } } catch (_) { await Future.delayed(const Duration(milliseconds: 500)); - add(WallSensorInitialEvent()); + add(WallSensorFetchStatusEvent()); } }); } @@ -117,17 +168,4 @@ class WallSensorBloc extends Bloc { BackToGridViewEvent event, Emitter emit) { emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); } - - FutureOr _fetchWallSensorBatchControl( - WallSensorBatchControlEvent event, Emitter emit) async { - emit(WallSensorLoadingInitialState()); - try { - var response = await DevicesManagementApi().getDeviceStatus(deviceId); - deviceStatus = WallSensorModel.fromJson(response.status); - emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); - } catch (e) { - emit(WallSensorFailedState(error: e.toString())); - return; - } - } } diff --git a/lib/pages/device_managment/wall_sensor/bloc/event.dart b/lib/pages/device_managment/wall_sensor/bloc/event.dart index c40d5e3e..f09c7123 100644 --- a/lib/pages/device_managment/wall_sensor/bloc/event.dart +++ b/lib/pages/device_managment/wall_sensor/bloc/event.dart @@ -7,7 +7,7 @@ abstract class WallSensorEvent extends Equatable { List get props => []; } -class WallSensorInitialEvent extends WallSensorEvent {} +class WallSensorFetchStatusEvent extends WallSensorEvent {} class WallSensorChangeValueEvent extends WallSensorEvent { final int value; @@ -18,8 +18,12 @@ class WallSensorChangeValueEvent extends WallSensorEvent { List get props => [value, code]; } -class WallSensorBatchControlEvent extends WallSensorEvent { - const WallSensorBatchControlEvent(); +class WallSensorFetchBatchStatusEvent extends WallSensorEvent { + final List devicesIds; + const WallSensorFetchBatchStatusEvent(this.devicesIds); + + @override + List get props => [devicesIds]; } class GetDeviceReportsEvent extends WallSensorEvent { @@ -40,3 +44,18 @@ class ShowDescriptionEvent extends WallSensorEvent { } class BackToGridViewEvent extends WallSensorEvent {} + +class WallSensorBatchControlEvent extends WallSensorEvent { + final List deviceIds; + final String code; + final dynamic value; + + const WallSensorBatchControlEvent({ + required this.deviceIds, + required this.code, + required this.value, + }); + + @override + List get props => [deviceIds, code, value]; +} diff --git a/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart index 703d129e..dab1e152 100644 --- a/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart +++ b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart @@ -22,7 +22,7 @@ class WallSensorBatchControlView extends StatelessWidget final isMedium = isMediumScreenSize(context); return BlocProvider( create: (context) => WallSensorBloc(deviceId: devicesIds.first) - ..add(const WallSensorBatchControlEvent()), + ..add(WallSensorFetchBatchStatusEvent(devicesIds)), child: BlocBuilder( builder: (context, state) { if (state is WallSensorLoadingInitialState || @@ -67,7 +67,8 @@ class WallSensorBatchControlView extends StatelessWidget steps: 1, action: (int value) { context.read().add( - WallSensorChangeValueEvent( + WallSensorBatchControlEvent( + deviceIds: devicesIds, code: 'motion_sensitivity_value', value: value, ), @@ -81,7 +82,8 @@ class WallSensorBatchControlView extends StatelessWidget maxValue: 5, steps: 1, action: (int value) => context.read().add( - WallSensorChangeValueEvent( + WallSensorBatchControlEvent( + deviceIds: devicesIds, code: 'motionless_sensitivity', value: value, ), @@ -95,7 +97,8 @@ class WallSensorBatchControlView extends StatelessWidget steps: 1, description: 'hr', action: (int value) => - context.read().add(WallSensorChangeValueEvent( + context.read().add(WallSensorBatchControlEvent( + deviceIds: devicesIds, code: 'no_one_time', value: value, ))), @@ -107,7 +110,8 @@ class WallSensorBatchControlView extends StatelessWidget steps: 75, description: 'cm', action: (int value) => context.read().add( - WallSensorChangeValueEvent( + WallSensorBatchControlEvent( + deviceIds: devicesIds, code: 'far_detection', value: value, ), diff --git a/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart b/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart index 375b01be..4f789477 100644 --- a/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart +++ b/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart @@ -26,8 +26,8 @@ class WallSensorControlsView extends StatelessWidget final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); return BlocProvider( - create: (context) => - WallSensorBloc(deviceId: device.uuid!)..add(WallSensorInitialEvent()), + create: (context) => WallSensorBloc(deviceId: device.uuid!) + ..add(WallSensorFetchStatusEvent()), child: BlocBuilder( builder: (context, state) { if (state is WallSensorLoadingInitialState || From 67667e44055a2f90d426c8d232ca29683b6c87a9 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 18 Sep 2024 14:09:23 +0300 Subject: [PATCH 20/65] push 3 gang batch control --- .../bloc/living_room_bloc.dart | 64 ++++++++++++++----- .../bloc/living_room_event.dart | 18 +++--- .../view/living_room_batch_controls.dart | 42 +++++++++++- .../view/living_room_device_control.dart | 2 +- 4 files changed, 98 insertions(+), 28 deletions(-) diff --git a/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart b/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart index 3bbe428c..4ab6f8e8 100644 --- a/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart +++ b/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart @@ -16,13 +16,14 @@ class LivingRoomBloc extends Bloc { Timer? _timer; LivingRoomBloc({required this.deviceId}) : super(LivingRoomInitial()) { - on(_onFetchDeviceStatus); + on(_onFetchDeviceStatus); on(_livingRoomControl); - on(_livingRoomBatchControl); + on(_livingRoomBatchControl); + on(_livingRoomFetchBatchControl); } - FutureOr _onFetchDeviceStatus( - LivingRoomFetchDeviceStatus event, Emitter emit) async { + FutureOr _onFetchDeviceStatus(LivingRoomFetchDeviceStatusEvent event, + Emitter emit) async { emit(LivingRoomDeviceStatusLoading()); try { final status = @@ -49,28 +50,44 @@ class LivingRoomBloc extends Bloc { value: event.value, oldValue: oldValue, emit: emit, + isBatch: false, ); } Future _runDebounce({ - required String deviceId, + required dynamic deviceId, required String code, required dynamic value, required dynamic oldValue, required Emitter emit, + required bool isBatch, }) async { + late String id; + + if (deviceId is List) { + id = deviceId.first; + } else { + id = deviceId; + } + if (_timer != null) { _timer!.cancel(); } _timer = Timer(const Duration(seconds: 1), () async { try { - final response = await DevicesManagementApi() - .deviceControl(deviceId, Status(code: code, value: value)); + late bool response; + if (isBatch) { + response = await DevicesManagementApi() + .deviceBatchControl(deviceId, code, value); + } else { + response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + } if (!response) { - _revertValueAndEmit(deviceId, code, oldValue, emit); + _revertValueAndEmit(id, code, oldValue, emit); } } catch (e) { - _revertValueAndEmit(deviceId, code, oldValue, emit); + _revertValueAndEmit(id, code, oldValue, emit); } }); } @@ -79,7 +96,6 @@ class LivingRoomBloc extends Bloc { Emitter emit) { _updateLocalValue(code, oldValue); emit(LivingRoomDeviceStatusLoaded(deviceStatus)); - emit(const LivingRoomControlError('Failed to control the device.')); } void _updateLocalValue(String code, dynamic value) { @@ -118,19 +134,35 @@ class LivingRoomBloc extends Bloc { } } - FutureOr _livingRoomBatchControl( - LivingRoomFetchBatchStatus event, Emitter emit) async { + FutureOr _livingRoomFetchBatchControl( + LivingRoomFetchBatchEvent event, Emitter emit) async { emit(LivingRoomDeviceStatusLoading()); try { - //TODO: get batch status from api - /// for now sending one id and getting the same value from fetch status final status = - await DevicesManagementApi().getDeviceStatus(event.deviceId); + await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = - LivingRoomStatusModel.fromJson(event.deviceId, status.status); + LivingRoomStatusModel.fromJson(event.devicesIds.first, status.status); emit(LivingRoomDeviceStatusLoaded(deviceStatus)); } catch (e) { emit(LivingRoomDeviceManagementError(e.toString())); } } + + FutureOr _livingRoomBatchControl( + LivingRoomBatchControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + + emit(LivingRoomDeviceStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.devicesIds, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: true, + ); + } } diff --git a/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart b/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart index 43a5b0c3..a3b0d78b 100644 --- a/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart +++ b/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart @@ -7,23 +7,23 @@ sealed class LivingRoomEvent extends Equatable { List get props => []; } -class LivingRoomFetchDeviceStatus extends LivingRoomEvent { +class LivingRoomFetchDeviceStatusEvent extends LivingRoomEvent { final String deviceId; - const LivingRoomFetchDeviceStatus(this.deviceId); + const LivingRoomFetchDeviceStatusEvent(this.deviceId); @override List get props => [deviceId]; } //LivingRoomFetchBatchStatus -class LivingRoomFetchBatchStatus extends LivingRoomEvent { - final String deviceId; +class LivingRoomFetchBatchEvent extends LivingRoomEvent { + final List devicesIds; - const LivingRoomFetchBatchStatus(this.deviceId); + const LivingRoomFetchBatchEvent(this.devicesIds); @override - List get props => [deviceId]; + List get props => [devicesIds]; } class LivingRoomControl extends LivingRoomEvent { @@ -39,13 +39,13 @@ class LivingRoomControl extends LivingRoomEvent { } class LivingRoomBatchControl extends LivingRoomEvent { - final List deviceId; + final List devicesIds; final String code; final bool value; const LivingRoomBatchControl( - {required this.deviceId, required this.code, required this.value}); + {required this.devicesIds, required this.code, required this.value}); @override - List get props => [deviceId, code, value]; + List get props => [devicesIds, code, value]; } diff --git a/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart index daff5e63..f8c40179 100644 --- a/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart +++ b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart @@ -17,7 +17,7 @@ class LivingRoomBatchControlsView extends StatelessWidget Widget build(BuildContext context) { return BlocProvider( create: (context) => LivingRoomBloc(deviceId: deviceIds.first) - ..add(LivingRoomFetchBatchStatus(deviceIds.first)), + ..add(LivingRoomFetchBatchEvent(deviceIds)), child: BlocBuilder( builder: (context, state) { if (state is LivingRoomDeviceStatusLoading) { @@ -61,7 +61,45 @@ class LivingRoomBatchControlsView extends StatelessWidget code: 'switch_1', deviceId: deviceIds.first, label: 'Wall Light', - onChange: (value) {}, + onChange: (value) { + context.read().add( + LivingRoomBatchControl( + devicesIds: deviceIds, + code: 'switch_1', + value: value, + ), + ); + }, + ), + ToggleWidget( + value: status.switch2, + code: 'switch_2', + deviceId: deviceIds.first, + label: 'Ceiling Light', + onChange: (value) { + context.read().add( + LivingRoomBatchControl( + devicesIds: deviceIds, + code: 'switch_2', + value: value, + ), + ); + }, + ), + ToggleWidget( + value: status.switch3, + code: 'switch_2', + deviceId: deviceIds.first, + label: 'Spotlight', + onChange: (value) { + context.read().add( + LivingRoomBatchControl( + devicesIds: deviceIds, + code: 'switch_3', + value: value, + ), + ); + }, ), FirmwareUpdateWidget( deviceId: deviceIds.first, diff --git a/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart b/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart index b8226f57..b7f97776 100644 --- a/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart +++ b/lib/pages/device_managment/three_gang_switch/view/living_room_device_control.dart @@ -15,7 +15,7 @@ class LivingRoomDeviceControlsView extends StatelessWidget Widget build(BuildContext context) { return BlocProvider( create: (context) => LivingRoomBloc(deviceId: deviceId) - ..add(LivingRoomFetchDeviceStatus(deviceId)), + ..add(LivingRoomFetchDeviceStatusEvent(deviceId)), child: BlocBuilder( builder: (context, state) { if (state is LivingRoomDeviceStatusLoading) { From bc309adba7a8b7a096cf1980bf3360dc361d4a98 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 18 Sep 2024 14:25:52 +0300 Subject: [PATCH 21/65] push 2 G switch batch control --- .../bloc/two_gang_switch_bloc.dart | 52 ++++++++++++++++--- .../bloc/two_gang_switch_event.dart | 6 +-- .../view/wall_light_batch_control.dart | 18 +++++-- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart index 7db9d56c..7a15a68c 100644 --- a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart +++ b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart @@ -12,6 +12,7 @@ class TwoGangSwitchBloc extends Bloc { on(_onFetchDeviceStatus); on(_onControl); on(_onFetchBatchStatus); + on(_onBatchControl); } late TwoGangStatusModel deviceStatus; @@ -46,30 +47,46 @@ class TwoGangSwitchBloc extends Bloc { value: event.value, oldValue: oldValue, emit: emit, + isBatch: false, ); } Future _runDebounce({ - required String deviceId, + required dynamic deviceId, required String code, required bool value, required bool oldValue, required Emitter emit, + required bool isBatch, }) async { + late String id; + + if (deviceId is List) { + id = deviceId.first; + } else { + id = deviceId; + } + if (_timer != null) { _timer!.cancel(); } _timer = Timer(const Duration(milliseconds: 500), () async { try { - final status = await DevicesManagementApi() - .deviceControl(deviceId, Status(code: code, value: value)); + late bool response; + if (isBatch) { + response = await DevicesManagementApi() + .deviceBatchControl(deviceId, code, value); + } else { + response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + } - if (!status) { - _revertValueAndEmit(deviceId, code, oldValue, emit); + if (!response) { + _revertValueAndEmit(id, code, oldValue, emit); } } catch (e) { - _revertValueAndEmit(deviceId, code, oldValue, emit); + _revertValueAndEmit(id, code, oldValue, emit); } }); } @@ -106,8 +123,9 @@ class TwoGangSwitchBloc extends Bloc { emit(TwoGangSwitchLoading()); try { final status = - await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status); + await DevicesManagementApi().getBatchStatus(event.devicesIds); + deviceStatus = + TwoGangStatusModel.fromJson(event.devicesIds.first, status.status); emit(TwoGangSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(TwoGangSwitchError(e.toString())); @@ -119,4 +137,22 @@ class TwoGangSwitchBloc extends Bloc { _timer?.cancel(); return super.close(); } + + FutureOr _onBatchControl( + TwoGangSwitchBatchControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + + emit(TwoGangSwitchStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: true, + ); + } } diff --git a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart index 2bacfc92..d5b9a01d 100644 --- a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart +++ b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart @@ -27,12 +27,12 @@ class TwoGangSwitchControl extends TwoGangSwitchEvent { } class TwoGangSwitchFetchBatchEvent extends TwoGangSwitchEvent { - final String deviceId; + final List devicesIds; - TwoGangSwitchFetchBatchEvent(this.deviceId); + TwoGangSwitchFetchBatchEvent(this.devicesIds); @override - List get props => [deviceId]; + List get props => [devicesIds]; } class TwoGangSwitchBatchControl extends TwoGangSwitchEvent { diff --git a/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart b/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart index 323bc3f8..1e417dfa 100644 --- a/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart +++ b/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart @@ -19,7 +19,7 @@ class TwoGangBatchControlView extends StatelessWidget Widget build(BuildContext context) { return BlocProvider( create: (context) => TwoGangSwitchBloc(deviceId: deviceIds.first) - ..add(TwoGangSwitchFetchBatchEvent(deviceIds.first)), + ..add(TwoGangSwitchFetchBatchEvent(deviceIds)), child: BlocBuilder( builder: (context, state) { if (state is TwoGangSwitchLoading) { @@ -62,14 +62,26 @@ class TwoGangBatchControlView extends StatelessWidget code: 'switch_1', deviceId: deviceIds.first, label: 'Wall Light', - onChange: (value) {}, + onChange: (value) { + context.read().add(TwoGangSwitchBatchControl( + deviceId: deviceIds, + code: 'switch_1', + value: value, + )); + }, ), ToggleWidget( value: status.switch2, code: 'switch_2', deviceId: deviceIds.first, label: 'Ceiling Light', - onChange: (value) {}, + onChange: (value) { + context.read().add(TwoGangSwitchBatchControl( + deviceId: deviceIds, + code: 'switch_2', + value: value, + )); + }, ), FirmwareUpdateWidget( deviceId: deviceIds.first, From 6d805ddfd70b07a2bffa22e7f7a1b8bab73dba69 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 18 Sep 2024 14:33:01 +0300 Subject: [PATCH 22/65] push one gang switch --- .../bloc/wall_light_switch_bloc.dart | 52 ++++++++++++++++--- .../bloc/wall_light_switch_event.dart | 12 ++--- .../view/wall_light_batch_control.dart | 12 ++++- 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart index 19517c92..ef25e7ac 100644 --- a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart +++ b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart @@ -14,6 +14,7 @@ class WallLightSwitchBloc on(_onFetchDeviceStatus); on(_onControl); on(_onFetchBatchStatus); + on(_onBatchControl); } late WallLightStatusModel deviceStatus; @@ -49,30 +50,47 @@ class WallLightSwitchBloc value: event.value, oldValue: oldValue, emit: emit, + isBatch: false, ); } Future _runDebounce({ - required String deviceId, + required dynamic deviceId, required String code, required bool value, required bool oldValue, required Emitter emit, + required bool isBatch, }) async { + late String id; + + if (deviceId is List) { + id = deviceId.first; + } else { + id = deviceId; + } + if (_timer != null) { _timer!.cancel(); } _timer = Timer(const Duration(milliseconds: 500), () async { try { - final status = await DevicesManagementApi() - .deviceControl(deviceId, Status(code: code, value: value)); + late bool response; - if (!status) { - _revertValueAndEmit(deviceId, code, oldValue, emit); + if (isBatch) { + response = await DevicesManagementApi() + .deviceBatchControl(deviceId, code, value); + } else { + response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + } + + if (!response) { + _revertValueAndEmit(id, code, oldValue, emit); } } catch (e) { - _revertValueAndEmit(deviceId, code, oldValue, emit); + _revertValueAndEmit(id, code, oldValue, emit); } }); } @@ -103,9 +121,9 @@ class WallLightSwitchBloc emit(WallLightSwitchLoading()); try { final status = - await DevicesManagementApi().getDeviceStatus(event.deviceId); + await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = - WallLightStatusModel.fromJson(event.deviceId, status.status); + WallLightStatusModel.fromJson(event.devicesIds.first, status.status); emit(WallLightSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(WallLightSwitchError(e.toString())); @@ -117,4 +135,22 @@ class WallLightSwitchBloc _timer?.cancel(); return super.close(); } + + FutureOr _onBatchControl(WallLightSwitchBatchControl event, + Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + + emit(WallLightSwitchStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.devicesIds, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: true, + ); + } } diff --git a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart index 59842692..88c86c97 100644 --- a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart +++ b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart @@ -27,22 +27,22 @@ class WallLightSwitchControl extends WallLightSwitchEvent { } class WallLightSwitchFetchBatchEvent extends WallLightSwitchEvent { - final String deviceId; + final List devicesIds; - WallLightSwitchFetchBatchEvent(this.deviceId); + WallLightSwitchFetchBatchEvent(this.devicesIds); @override - List get props => [deviceId]; + List get props => [devicesIds]; } class WallLightSwitchBatchControl extends WallLightSwitchEvent { - final List deviceId; + final List devicesIds; final String code; final bool value; WallLightSwitchBatchControl( - {required this.deviceId, required this.code, required this.value}); + {required this.devicesIds, required this.code, required this.value}); @override - List get props => [deviceId, code, value]; + List get props => [devicesIds, code, value]; } diff --git a/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart b/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart index ffdcfa85..0c58c6f5 100644 --- a/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart +++ b/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart @@ -19,7 +19,7 @@ class WallLightBatchControlView extends StatelessWidget Widget build(BuildContext context) { return BlocProvider( create: (context) => WallLightSwitchBloc(deviceId: deviceIds.first) - ..add(WallLightSwitchFetchBatchEvent(deviceIds.first)), + ..add(WallLightSwitchFetchBatchEvent(deviceIds)), child: BlocBuilder( builder: (context, state) { if (state is WallLightSwitchLoading) { @@ -63,7 +63,15 @@ class WallLightBatchControlView extends StatelessWidget code: 'switch_1', deviceId: deviceIds.first, label: 'Wall Light', - onChange: (value) {}, + onChange: (value) { + context.read().add( + WallLightSwitchBatchControl( + devicesIds: deviceIds, + code: 'switch_1', + value: value, + ), + ); + }, ), FirmwareUpdateWidget( deviceId: deviceIds.first, From 619d964cd7a4e65eccfca8bcf574424fcafa74e4 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 18 Sep 2024 15:08:43 +0300 Subject: [PATCH 23/65] push curtain batch control --- lib/pages/common/curtain_toggle.dart | 15 ++---- .../curtain/bloc/curtain_bloc.dart | 47 ++++++++++++++++--- .../curtain/bloc/curtain_event.dart | 18 +++++-- .../view/curtain_batch_status_view.dart | 9 +++- .../curtain/view/curtain_status_view.dart | 9 ++++ .../shared/table/report_table.dart | 1 + 6 files changed, 77 insertions(+), 22 deletions(-) diff --git a/lib/pages/common/curtain_toggle.dart b/lib/pages/common/curtain_toggle.dart index 371f8833..a44822c3 100644 --- a/lib/pages/common/curtain_toggle.dart +++ b/lib/pages/common/curtain_toggle.dart @@ -1,9 +1,6 @@ import 'package:flutter/cupertino.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/device_managment/curtain/bloc/curtain_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -12,6 +9,7 @@ class CurtainToggle extends StatelessWidget { final String code; final String deviceId; final String label; + final Null Function(dynamic value) onChanged; const CurtainToggle({ super.key, @@ -19,6 +17,7 @@ class CurtainToggle extends StatelessWidget { required this.code, required this.deviceId, required this.label, + required this.onChanged, }); @override @@ -54,15 +53,7 @@ class CurtainToggle extends StatelessWidget { child: CupertinoSwitch( value: value, activeColor: ColorsManager.dialogBlueTitle, - onChanged: (newValue) { - context.read().add( - CurtainControl( - deviceId: deviceId, - code: code, - value: newValue, - ), - ); - }, + onChanged: onChanged, ), ), ], diff --git a/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart index 484e2b80..eb031552 100644 --- a/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart +++ b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart @@ -14,6 +14,7 @@ class CurtainBloc extends Bloc { on(_onFetchDeviceStatus); on(_onFetchBatchStatus); on(_onCurtainControl); + on(_onCurtainBatchControl); } FutureOr _onFetchDeviceStatus( @@ -45,16 +46,26 @@ class CurtainBloc extends Bloc { value: event.value, oldValue: oldValue, emit: emit, + isBatch: false, ); } Future _runDebounce({ - required String deviceId, + required dynamic deviceId, required String code, required bool value, required bool oldValue, required Emitter emit, + required bool isBatch, }) async { + late String id; + + if (deviceId is List) { + id = deviceId.first; + } else { + id = deviceId; + } + if (_timer != null) { _timer!.cancel(); } @@ -62,14 +73,20 @@ class CurtainBloc extends Bloc { try { final controlValue = value ? 'open' : 'close'; - final response = await DevicesManagementApi() - .deviceControl(deviceId, Status(code: code, value: controlValue)); + late bool response; + if (isBatch) { + response = await DevicesManagementApi() + .deviceBatchControl(deviceId, code, controlValue); + } else { + response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: controlValue)); + } if (!response) { - _revertValueAndEmit(deviceId, oldValue, emit); + _revertValueAndEmit(id, oldValue, emit); } } catch (e) { - _revertValueAndEmit(deviceId, oldValue, emit); + _revertValueAndEmit(id, oldValue, emit); } }); } @@ -95,7 +112,7 @@ class CurtainBloc extends Bloc { emit(CurtainStatusLoading()); try { final status = - await DevicesManagementApi().getDeviceStatus(event.deviceId); + await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = _checkStatus(status.status[0].value); @@ -104,4 +121,22 @@ class CurtainBloc extends Bloc { emit(CurtainError(e.toString())); } } + + FutureOr _onCurtainBatchControl( + CurtainBatchControl event, Emitter emit) async { + final oldValue = deviceStatus; + + _updateLocalValue(event.value, emit); + + emit(CurtainStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.devicesIds, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: true, + ); + } } diff --git a/lib/pages/device_managment/curtain/bloc/curtain_event.dart b/lib/pages/device_managment/curtain/bloc/curtain_event.dart index 98670868..8ef85145 100644 --- a/lib/pages/device_managment/curtain/bloc/curtain_event.dart +++ b/lib/pages/device_managment/curtain/bloc/curtain_event.dart @@ -17,12 +17,12 @@ class CurtainFetchDeviceStatus extends CurtainEvent { } class CurtainFetchBatchStatus extends CurtainEvent { - final String deviceId; + final List devicesIds; - const CurtainFetchBatchStatus(this.deviceId); + const CurtainFetchBatchStatus(this.devicesIds); @override - List get props => [deviceId]; + List get props => [devicesIds]; } class CurtainControl extends CurtainEvent { @@ -36,3 +36,15 @@ class CurtainControl extends CurtainEvent { @override List get props => [deviceId, code, value]; } + +class CurtainBatchControl extends CurtainEvent { + final List devicesIds; + final String code; + final bool value; + + const CurtainBatchControl( + {required this.devicesIds, required this.code, required this.value}); + + @override + List get props => [devicesIds, code, value]; +} diff --git a/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart b/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart index 27a71e89..ec1a0076 100644 --- a/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart +++ b/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart @@ -18,7 +18,7 @@ class CurtainBatchStatusView extends StatelessWidget Widget build(BuildContext context) { return BlocProvider( create: (context) => CurtainBloc(deviceId: devicesIds.first) - ..add(CurtainFetchDeviceStatus(devicesIds.first)), + ..add(CurtainFetchBatchStatus(devicesIds)), child: BlocBuilder( builder: (context, state) { if (state is CurtainStatusLoading) { @@ -59,6 +59,13 @@ class CurtainBatchStatusView extends StatelessWidget code: 'control', deviceId: devicesIds.first, label: 'Curtains', + onChanged: (value) { + context.read().add(CurtainBatchControl( + devicesIds: devicesIds, + code: 'control', + value: value, + )); + }, ), FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5), FactoryResetWidget(deviceId: devicesIds.first), diff --git a/lib/pages/device_managment/curtain/view/curtain_status_view.dart b/lib/pages/device_managment/curtain/view/curtain_status_view.dart index 7e1eed1d..2afe49f4 100644 --- a/lib/pages/device_managment/curtain/view/curtain_status_view.dart +++ b/lib/pages/device_managment/curtain/view/curtain_status_view.dart @@ -58,6 +58,15 @@ class CurtainStatusControlsView extends StatelessWidget code: 'control', deviceId: deviceId, label: 'Curtains', + onChanged: (value) { + context.read().add( + CurtainControl( + deviceId: deviceId, + code: 'control', + value: value, + ), + ); + }, ), const SizedBox.shrink(), ], diff --git a/lib/pages/device_managment/shared/table/report_table.dart b/lib/pages/device_managment/shared/table/report_table.dart index 3cddef37..7dda10e1 100644 --- a/lib/pages/device_managment/shared/table/report_table.dart +++ b/lib/pages/device_managment/shared/table/report_table.dart @@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_rep import 'package:syncrow_web/pages/device_managment/shared/table/table_cell_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/table_header.dart'; +// ignore: must_be_immutable class ReportsTable extends StatelessWidget { final DeviceReport report; final String? thirdColumnTitle; From 536ac8857ea464020e64ed5a220d7e19ea3ccdc4 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 18 Sep 2024 18:01:22 +0300 Subject: [PATCH 24/65] push notification dialog design --- .../all_devices/models/devices_model.dart | 133 +++++++++++------- .../door_lock/bloc/door_lock_event.dart | 2 + .../gateway/view/gateway_view.dart | 2 +- .../view/main_door_control_view.dart | 12 +- .../widgets/notification_dialog.dart | 97 +++++++++++++ .../shared/toggle_widget.dart | 25 ++-- lib/pages/home/view/home_page_mobile.dart | 9 +- 7 files changed, 211 insertions(+), 69 deletions(-) create mode 100644 lib/pages/device_managment/main_door_sensor/widgets/notification_dialog.dart diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index a814a8b1..e81b7695 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -1,6 +1,6 @@ - import 'package:syncrow_web/pages/device_managment/all_devices/models/room.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/unit.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; class AllDevicesModel { /* @@ -106,7 +106,7 @@ class AllDevicesModel { categoryName = json['categoryName']?.toString(); createTime = int.tryParse(json['createTime']?.toString() ?? ''); gatewayId = json['gatewayId']?.toString(); - icon = json['icon']?.toString(); + icon = json['icon'] ?? _getDefaultIcon(productType); ip = json['ip']?.toString(); lat = json['lat']?.toString(); localKey = json['localKey']?.toString(); @@ -122,6 +122,35 @@ class AllDevicesModel { uuid = json['uuid']?.toString(); batteryLevel = int.tryParse(json['battery']?.toString() ?? ''); } + + String _getDefaultIcon(String? productType) { + switch (productType) { + case 'LightBulb': + return Assets.lightBulb; + case 'CeilingSensor': + case 'WallSensor': + return Assets.sensors; + case 'AC': + return Assets.ac; + case 'DoorLock': + return Assets.doorLock; + case 'Curtain': + return Assets.curtain; + case '3G': + case '2G': + case '1G': + return Assets.gangSwitch; + case 'Gateway': + return Assets.gateway; + case 'WH': + return Assets.blackLogo; + case 'DS': + return Assets.sensors; + default: + return Assets.logo; + } + } + Map toJson() { final data = {}; if (room != null) { @@ -159,61 +188,61 @@ class AllDevicesModel { @override bool operator ==(Object other) { if (identical(this, other)) return true; - + return other is AllDevicesModel && - other.room == room && - other.unit == unit && - other.productUuid == productUuid && - other.productType == productType && - other.permissionType == permissionType && - other.activeTime == activeTime && - other.category == category && - other.categoryName == categoryName && - other.createTime == createTime && - other.gatewayId == gatewayId && - other.icon == icon && - other.ip == ip && - other.lat == lat && - other.localKey == localKey && - other.lon == lon && - other.model == model && - other.name == name && - other.nodeId == nodeId && - other.online == online && - other.ownerId == ownerId && - other.sub == sub && - other.timeZone == timeZone && - other.updateTime == updateTime && - other.uuid == uuid && - other.batteryLevel == batteryLevel; + other.room == room && + other.unit == unit && + other.productUuid == productUuid && + other.productType == productType && + other.permissionType == permissionType && + other.activeTime == activeTime && + other.category == category && + other.categoryName == categoryName && + other.createTime == createTime && + other.gatewayId == gatewayId && + other.icon == icon && + other.ip == ip && + other.lat == lat && + other.localKey == localKey && + other.lon == lon && + other.model == model && + other.name == name && + other.nodeId == nodeId && + other.online == online && + other.ownerId == ownerId && + other.sub == sub && + other.timeZone == timeZone && + other.updateTime == updateTime && + other.uuid == uuid && + other.batteryLevel == batteryLevel; } @override int get hashCode { return room.hashCode ^ - unit.hashCode ^ - productUuid.hashCode ^ - productType.hashCode ^ - permissionType.hashCode ^ - activeTime.hashCode ^ - category.hashCode ^ - categoryName.hashCode ^ - createTime.hashCode ^ - gatewayId.hashCode ^ - icon.hashCode ^ - ip.hashCode ^ - lat.hashCode ^ - localKey.hashCode ^ - lon.hashCode ^ - model.hashCode ^ - name.hashCode ^ - nodeId.hashCode ^ - online.hashCode ^ - ownerId.hashCode ^ - sub.hashCode ^ - timeZone.hashCode ^ - updateTime.hashCode ^ - uuid.hashCode ^ - batteryLevel.hashCode; + unit.hashCode ^ + productUuid.hashCode ^ + productType.hashCode ^ + permissionType.hashCode ^ + activeTime.hashCode ^ + category.hashCode ^ + categoryName.hashCode ^ + createTime.hashCode ^ + gatewayId.hashCode ^ + icon.hashCode ^ + ip.hashCode ^ + lat.hashCode ^ + localKey.hashCode ^ + lon.hashCode ^ + model.hashCode ^ + name.hashCode ^ + nodeId.hashCode ^ + online.hashCode ^ + ownerId.hashCode ^ + sub.hashCode ^ + timeZone.hashCode ^ + updateTime.hashCode ^ + uuid.hashCode ^ + batteryLevel.hashCode; } } diff --git a/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart b/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart index 8ee2e6aa..4033676f 100644 --- a/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart +++ b/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart @@ -37,3 +37,5 @@ class UpdateLockEvent extends DoorLockEvent { @override List get props => [value]; } + + diff --git a/lib/pages/device_managment/gateway/view/gateway_view.dart b/lib/pages/device_managment/gateway/view/gateway_view.dart index 718e4d84..ad760a14 100644 --- a/lib/pages/device_managment/gateway/view/gateway_view.dart +++ b/lib/pages/device_managment/gateway/view/gateway_view.dart @@ -86,7 +86,7 @@ class _DeviceItem extends StatelessWidget { const Spacer(), Text( device.name ?? 'Unknown Device', - textAlign: TextAlign.center, + textAlign: TextAlign.start, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, diff --git a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart index 657ebfee..2168be5a 100644 --- a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart +++ b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart @@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_do import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/widgets/notification_dialog.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart'; import 'package:syncrow_web/utils/color_manager.dart'; @@ -82,7 +83,7 @@ class MainDoorSensorControlView extends StatelessWidget paddingAmount: 8, ), IconNameStatusContainer( - name: 'Open/Close\n Record', + name: 'Open/Close\nRecord', icon: Assets.mainDoorReports, onTap: () { final from = DateTime.now() @@ -102,9 +103,14 @@ class MainDoorSensorControlView extends StatelessWidget textColor: ColorsManager.blackColor, ), IconNameStatusContainer( - name: 'Notifications\n Settings', + name: 'Notifications\nSettings', icon: Assets.mainDoorNotifi, - onTap: () {}, + onTap: () { + showDialog( + context: context, + builder: (context) => const NotificationDialog(), + ); + }, status: false, textColor: ColorsManager.blackColor, ), diff --git a/lib/pages/device_managment/main_door_sensor/widgets/notification_dialog.dart b/lib/pages/device_managment/main_door_sensor/widgets/notification_dialog.dart new file mode 100644 index 00000000..d452026d --- /dev/null +++ b/lib/pages/device_managment/main_door_sensor/widgets/notification_dialog.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class NotificationDialog extends StatelessWidget { + const NotificationDialog({super.key}); + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: Colors.white, + insetPadding: const EdgeInsets.all(20), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: SizedBox( + width: 798, + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(), + Text( + 'Notification Settings', + 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(); + }, + ), + ), + ], + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ToggleWidget( + value: true, + code: 'notification', + deviceId: '', + label: 'Low Battery', + onChange: (v) {}, + icon: '-1', + ), + ToggleWidget( + value: true, + code: 'notification', + deviceId: '', + label: 'Closing\nReminders', + onChange: (v) {}, + icon: '-1', + ), + ToggleWidget( + value: true, + code: 'notification', + deviceId: '', + label: 'Door Alarm', + onChange: (v) {}, + icon: '-1', + ), + ], + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/shared/toggle_widget.dart b/lib/pages/device_managment/shared/toggle_widget.dart index 71eba26f..5f88b106 100644 --- a/lib/pages/device_managment/shared/toggle_widget.dart +++ b/lib/pages/device_managment/shared/toggle_widget.dart @@ -40,16 +40,21 @@ class ToggleWidget extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - ClipOval( - child: Container( - color: ColorsManager.whiteColors, - child: SvgPicture.asset( - icon ?? Assets.lightPulp, - width: 60, - height: 60, - fit: BoxFit.cover, - ), - )), + icon == '-1' + ? const SizedBox( + height: 60, + width: 60, + ) + : ClipOval( + child: Container( + color: ColorsManager.whiteColors, + child: SvgPicture.asset( + icon ?? Assets.lightPulp, + width: 60, + height: 60, + fit: BoxFit.cover, + ), + )), Text( label, style: context.textTheme.titleMedium!.copyWith( diff --git a/lib/pages/home/view/home_page_mobile.dart b/lib/pages/home/view/home_page_mobile.dart index 560ef0d5..8f72f8cb 100644 --- a/lib/pages/home/view/home_page_mobile.dart +++ b/lib/pages/home/view/home_page_mobile.dart @@ -41,7 +41,8 @@ class HomeMobilePage extends StatelessWidget { SizedBox(height: size.height * 0.05), const Text( 'ACCESS YOUR APPS', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700), + style: + TextStyle(fontSize: 20, fontWeight: FontWeight.w700), ), const SizedBox(height: 30), Expanded( @@ -51,7 +52,8 @@ class HomeMobilePage extends StatelessWidget { width: size.width * 0.68, child: GridView.builder( itemCount: 8, - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 20.0, mainAxisSpacing: 20.0, @@ -63,7 +65,8 @@ class HomeMobilePage extends StatelessWidget { active: homeItems[index]['active'], name: homeItems[index]['title'], img: homeItems[index]['icon'], - onTap: () => homeBloc.homeItems[index].onPress(context), + onTap: () => + homeBloc.homeItems[index].onPress(context), ); }, ), From ba95f6774bd72f0c8d185998272bd7c80cd5c45f Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 18 Sep 2024 21:29:02 +0300 Subject: [PATCH 25/65] push schedual counddown design --- .../view/water_heater_device_control.dart | 195 +--------- .../water_heater/widgets/schedual_view.dart | 361 ++++++++++++++++++ 2 files changed, 374 insertions(+), 182 deletions(-) create mode 100644 lib/pages/device_managment/water_heater/widgets/schedual_view.dart diff --git a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart index 14193ee0..5974e735 100644 --- a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart +++ b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart @@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/device_managment/shared/device_controls_contai import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.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/water_heater_status_model.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedual_view.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'; @@ -29,7 +30,8 @@ class WaterHeaterDeviceControl extends StatelessWidget } else if (state is WaterHeaterDeviceStatusLoaded) { return _buildStatusControls(context, state.status); } else if (state is WaterHeaterScheduleViewState) { - return _buildScheduleView(context, state); + final status = context.read().deviceStatus; + return _buildStatusControls(context, status); } else if (state is WaterHeaterFailedState || state is WaterHeaterBatchFailedState) { return const Center(child: Text('Error fetching status')); @@ -41,7 +43,9 @@ class WaterHeaterDeviceControl extends StatelessWidget } Widget _buildStatusControls( - BuildContext context, WaterHeaterStatusModel status) { + BuildContext context, + WaterHeaterStatusModel status, + ) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -71,7 +75,13 @@ class WaterHeaterDeviceControl extends StatelessWidget ), GestureDetector( onTap: () { - // context.read().add(const ShowScheduleViewEvent()); + context.read().add(const ShowScheduleViewEvent()); + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildScheduleView(status: status), + )); }, child: DeviceControlsContainer( child: Column( @@ -109,183 +119,4 @@ class WaterHeaterDeviceControl extends StatelessWidget ], ); } - - Widget _buildScheduleView( - BuildContext context, WaterHeaterScheduleViewState state) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Scheduling', - style: context.textTheme.titleLarge!.copyWith( - fontWeight: FontWeight.bold, - color: ColorsManager.dialogBlueTitle, - ), - ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: ListTile( - title: const Text('Countdown'), - leading: Radio( - value: ScheduleModes.countdown, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - context.read().add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.hours, - minutes: state.minutes, - )); - } - }, - ), - ), - ), - Expanded( - child: ListTile( - title: const Text('Schedule'), - leading: Radio( - value: ScheduleModes.schedule, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - context.read().add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.hours, - minutes: state.minutes, - )); - } - }, - ), - ), - ), - Expanded( - child: ListTile( - title: const Text('Circulate'), - leading: Radio( - value: ScheduleModes.circulate, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - context.read().add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.hours, - minutes: state.minutes, - )); - } - }, - ), - ), - ), - Expanded( - child: ListTile( - title: const Text('Inching'), - leading: Radio( - value: ScheduleModes.inching, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - context.read().add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.hours, - minutes: state.minutes, - )); - } - }, - ), - ), - ), - ], - ), - const SizedBox(height: 20), - if (state.scheduleMode == ScheduleModes.countdown || - state.scheduleMode == ScheduleModes.inching) ...[ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Hours input - _buildTimeInputField( - label: 'h', - initialValue: state.hours.toString(), - onChanged: (value) { - int hours = int.tryParse(value) ?? 0; - context.read().add(UpdateScheduleEvent( - scheduleMode: state.scheduleMode, - hours: hours, - minutes: state.minutes, - )); - }, - ), - const SizedBox(width: 10), - // Minutes input - _buildTimeInputField( - label: 'm', - initialValue: state.minutes.toString(), - onChanged: (value) { - int minutes = int.tryParse(value) ?? 0; - context.read().add(UpdateScheduleEvent( - scheduleMode: state.scheduleMode, - hours: state.hours, - minutes: minutes, - )); - }, - ), - ], - ), - ], - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () { - Navigator.of(context) - .pop(); // Close the dialog or scheduling view - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.grey[400], - ), - child: const Text('Cancel'), - ), - const SizedBox(width: 20), - ElevatedButton( - onPressed: () { - // Handle saving schedule logic - }, - child: const Text('Save'), - ), - ], - ), - ], - ), - ); - } - - Widget _buildTimeInputField({ - required String label, - required String initialValue, - required Function(String) onChanged, - }) { - return Column( - children: [ - Text(label, style: const TextStyle(fontSize: 18)), - SizedBox( - width: 50, - child: TextField( - keyboardType: TextInputType.number, - textAlign: TextAlign.center, - decoration: const InputDecoration(border: UnderlineInputBorder()), - onChanged: onChanged, - controller: TextEditingController(text: initialValue), - ), - ), - ], - ); - } } diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart new file mode 100644 index 00000000..411621eb --- /dev/null +++ b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart @@ -0,0 +1,361 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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/water_heater_status_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class BuildScheduleView extends StatelessWidget { + const BuildScheduleView({super.key, required this.status}); + + final WaterHeaterStatusModel status; + + @override + Widget build(BuildContext context) { + return 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( + builder: (context, state) { + if (state is WaterHeaterScheduleViewState) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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(); + }, + ), + ), + ], + ), + const SizedBox(height: 20), + Text( + 'Type:', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + const SizedBox( + height: 4, + ), + SizedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Flexible( + child: ListTile( + contentPadding: EdgeInsets.zero, + title: Text( + 'Countdown', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.blackColor, + ), + ), + leading: Radio( + value: ScheduleModes.countdown, + groupValue: state.scheduleMode, + onChanged: (ScheduleModes? value) { + if (value != null) { + context + .read() + .add(UpdateScheduleEvent( + scheduleMode: value, + hours: state.hours, + minutes: state.minutes, + )); + } + }, + ), + ), + ), + Flexible( + child: ListTile( + contentPadding: EdgeInsets.zero, + title: Text( + 'Schedule', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.blackColor, + ), + ), + leading: Radio( + value: ScheduleModes.schedule, + groupValue: state.scheduleMode, + onChanged: (ScheduleModes? value) { + if (value != null) { + context + .read() + .add(UpdateScheduleEvent( + scheduleMode: value, + hours: state.hours, + minutes: state.minutes, + )); + } + }, + ), + ), + ), + Flexible( + child: ListTile( + title: Text( + 'Circulate', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.blackColor, + ), + ), + leading: Radio( + value: ScheduleModes.circulate, + groupValue: state.scheduleMode, + onChanged: (ScheduleModes? value) { + if (value != null) { + context + .read() + .add(UpdateScheduleEvent( + scheduleMode: value, + hours: state.hours, + minutes: state.minutes, + )); + } + }, + ), + ), + ), + Flexible( + child: ListTile( + title: Text( + 'Inching', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.blackColor, + ), + ), + leading: Radio( + value: ScheduleModes.inching, + groupValue: state.scheduleMode, + onChanged: (ScheduleModes? value) { + if (value != null) { + context + .read() + .add(UpdateScheduleEvent( + scheduleMode: value, + hours: state.hours, + minutes: state.minutes, + )); + } + }, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 20), + if (state.scheduleMode == ScheduleModes.countdown || + state.scheduleMode == ScheduleModes.inching) ...[ + Text( + 'Countdown:', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + const SizedBox(height: 4), + _hourMinutesWheel(state, context) + ], + const SizedBox(height: 20), + Center( + child: SizedBox( + width: 400, + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + 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: DefaultButton( + height: 40, + onPressed: () {}, + backgroundColor: ColorsManager.primaryColor, + child: const Text('Save'), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } + return const SizedBox(); + }, + ), + ), + ), + ), + ); + } + + Row _hourMinutesWheel( + WaterHeaterScheduleViewState state, BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + // Hours Picker + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 50, + width: 80, + padding: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(8), + ), + child: ListWheelScrollView.useDelegate( + controller: + FixedExtentScrollController(initialItem: state.hours), + itemExtent: 40.0, + physics: FixedExtentScrollPhysics(), + onSelectedItemChanged: (int value) { + context.read().add( + UpdateScheduleEvent( + scheduleMode: state.scheduleMode, + hours: value, + minutes: state.minutes, + ), + ); + }, + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) { + return Center( + child: Text( + index.toString().padLeft(2, '0'), + style: const TextStyle(fontSize: 24), + ), + ); + }, + childCount: 24, + ), + ), + ), + const SizedBox(height: 8), + const Text( + 'h', + style: TextStyle( + color: ColorsManager.grayColor, + fontSize: 18, + ), + ), + ], + ), + const SizedBox(width: 10), + // Minutes Picker + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 50, + width: 80, + padding: const EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.circular(8), + ), + child: ListWheelScrollView.useDelegate( + controller: + FixedExtentScrollController(initialItem: state.minutes), + itemExtent: 40.0, + physics: FixedExtentScrollPhysics(), + onSelectedItemChanged: (int value) { + context.read().add( + UpdateScheduleEvent( + scheduleMode: state.scheduleMode, + hours: state.hours, + minutes: value, + ), + ); + }, + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) { + return Center( + child: Text( + index.toString().padLeft(2, '0'), + style: const TextStyle(fontSize: 24), + ), + ); + }, + childCount: 60, + ), + ), + ), + const SizedBox(height: 8), + const Text( + 'm', + style: TextStyle( + color: ColorsManager.grayColor, + fontSize: 18, + ), + ), + ], + ), + ], + ); + } +} From b3807f2980d154004f40e8da207b6a33894efa78 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Thu, 19 Sep 2024 01:31:52 +0300 Subject: [PATCH 26/65] push save and stop buttons --- .../water_heater/bloc/water_heater_bloc.dart | 139 ++++++++++++++---- .../water_heater/bloc/water_heater_event.dart | 6 +- .../water_heater/bloc/water_heater_state.dart | 5 +- .../models/water_heater_status_model.dart | 9 +- .../view/water_heater_device_control.dart | 2 +- .../water_heater/widgets/schedual_view.dart | 63 +++++++- 6 files changed, 179 insertions(+), 45 deletions(-) diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index 43372b2d..e095d425 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -12,7 +12,6 @@ class WaterHeaterBloc extends Bloc { WaterHeaterBloc() : super(WaterHeaterInitial()) { on(_fetchWaterHeaterStatus); on(_controlWaterHeater); - on(_showScheduleView); on(_updateScheduleEvent); on(_stopScheduleEvent); } @@ -23,37 +22,23 @@ class WaterHeaterBloc extends Bloc { FutureOr _updateScheduleEvent( UpdateScheduleEvent event, Emitter emit, - ) { + ) async { + final currentState = state as WaterHeaterScheduleViewState; + + final countdownRemaining = currentState.isActive + ? currentState.countdownRemaining + : Duration(hours: event.hours, minutes: event.minutes); + emit(WaterHeaterScheduleViewState( scheduleMode: event.scheduleMode, - hours: event.hours, - minutes: event.minutes, - isActive: true, + hours: countdownRemaining!.inHours, + minutes: countdownRemaining.inMinutes % 60, + isActive: currentState.isActive, + countdownRemaining: countdownRemaining, )); - } - FutureOr _showScheduleView( - ShowScheduleViewEvent event, Emitter emit) { - emit(const WaterHeaterScheduleViewState( - scheduleMode: ScheduleModes.countdown, - hours: 6, - minutes: 23, - isActive: false, - )); - } - - FutureOr _stopScheduleEvent( - StopScheduleEvent event, - Emitter emit, - ) { - if (state is WaterHeaterScheduleViewState) { - final currentState = state as WaterHeaterScheduleViewState; - emit(WaterHeaterScheduleViewState( - scheduleMode: currentState.scheduleMode, - hours: currentState.hours, - minutes: currentState.minutes, - isActive: false, - )); + if (currentState.isActive) { + _startCountdown(countdownRemaining, emit); } } @@ -65,22 +50,39 @@ class WaterHeaterBloc extends Bloc { emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); - await _runDebounce( + final success = await _runDebounce( deviceId: event.deviceId, code: event.code, value: event.value, oldValue: oldValue, emit: emit, ); + + if (success && + (event.code == "countdown_1" || event.code == "switch_inching")) { + final countdownDuration = Duration(seconds: event.value); + + emit(WaterHeaterScheduleViewState( + scheduleMode: deviceStatus.scheduleMode, + hours: countdownDuration.inHours, + minutes: (countdownDuration.inMinutes % 60), + isActive: true, + countdownRemaining: countdownDuration, + )); + + _startCountdown(countdownDuration, emit); + } } - Future _runDebounce({ + Future _runDebounce({ required String deviceId, required String code, required dynamic value, required dynamic oldValue, required Emitter emit, }) async { + final completer = Completer(); + if (_timer != null) { _timer!.cancel(); } @@ -94,11 +96,17 @@ class WaterHeaterBloc extends Bloc { if (!status) { _revertValueAndEmit(deviceId, code, oldValue, emit); + completer.complete(false); + } else { + completer.complete(true); } } catch (e) { _revertValueAndEmit(deviceId, code, oldValue, emit); + completer.complete(false); } }); + + return completer.future; } void _revertValueAndEmit(String deviceId, String code, dynamic oldValue, @@ -140,12 +148,81 @@ class WaterHeaterBloc extends Bloc { deviceStatus = WaterHeaterStatusModel.fromJson(event.deviceId, status.status); - emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); + if (deviceStatus.countdownHours > 0 || + deviceStatus.countdownMinutes > 0) { + final remainingDuration = Duration( + hours: deviceStatus.countdownHours, + minutes: deviceStatus.countdownMinutes, + ); + + emit(WaterHeaterScheduleViewState( + scheduleMode: deviceStatus.scheduleMode, + hours: deviceStatus.countdownHours, + minutes: deviceStatus.countdownMinutes, + isActive: true, + countdownRemaining: remainingDuration, + )); + + _startCountdown(remainingDuration, emit); + } else { + emit(WaterHeaterScheduleViewState( + scheduleMode: deviceStatus.scheduleMode, + hours: 0, + minutes: 0, + isActive: false, + )); + } } catch (e) { emit(WaterHeaterFailedState(error: e.toString())); } } + void _startCountdown(Duration duration, Emitter emit) { + _timer?.cancel(); + + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + final state = this.state as WaterHeaterScheduleViewState; + final remaining = state.countdownRemaining! - const Duration(seconds: 1); + + if (remaining.isNegative || remaining == Duration.zero) { + _timer?.cancel(); + emit(WaterHeaterScheduleViewState( + scheduleMode: state.scheduleMode, + hours: 0, + minutes: 0, + isActive: false, + countdownRemaining: Duration.zero, + )); + } else { + emit(WaterHeaterScheduleViewState( + scheduleMode: state.scheduleMode, + hours: remaining.inHours, + minutes: remaining.inMinutes % 60, + isActive: true, + countdownRemaining: remaining, + )); + } + }); + } + + FutureOr _stopScheduleEvent( + StopScheduleEvent event, + Emitter emit, + ) { + _timer?.cancel(); + deviceStatus = deviceStatus.copyWith( + countdownHours: 0, + countdownMinutes: 0, + scheduleMode: ScheduleModes.countdown, + ); + emit(const WaterHeaterScheduleViewState( + scheduleMode: ScheduleModes.countdown, + hours: 0, + minutes: 0, + isActive: false, + )); + } + @override Future close() { _timer?.cancel(); diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index 4c1354f9..a0164b3a 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -51,6 +51,6 @@ class WaterHeaterFetchBatchStatusEvent extends WaterHeaterEvent { List get props => [deviceId]; } -class ShowScheduleViewEvent extends WaterHeaterEvent { - const ShowScheduleViewEvent(); -} +// class ShowScheduleViewEvent extends WaterHeaterEvent { +// const ShowScheduleViewEvent(); +// } diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart index a4216e44..2dc45367 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart @@ -45,14 +45,17 @@ class WaterHeaterScheduleViewState extends WaterHeaterState { final int hours; final int minutes; final bool isActive; + final Duration? countdownRemaining; const WaterHeaterScheduleViewState({ required this.scheduleMode, required this.hours, required this.minutes, required this.isActive, + this.countdownRemaining, }); @override - List get props => [scheduleMode, hours, minutes]; + List get props => + [scheduleMode, hours, minutes, isActive, countdownRemaining]; } diff --git a/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart b/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart index 3cf29a96..fd16df50 100644 --- a/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart +++ b/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart @@ -23,8 +23,7 @@ class WaterHeaterStatusModel { factory WaterHeaterStatusModel.fromJson(String id, List jsonList) { late bool heaterSwitch = false; - late int countdownHours = 0; - late int countdownMinutes = 0; + late int countdownInSeconds = 0; late String relayStatus = ''; late String cycleTiming = ''; late ScheduleModes scheduleMode = ScheduleModes.countdown; @@ -35,7 +34,7 @@ class WaterHeaterStatusModel { heaterSwitch = status.value ?? false; break; case 'countdown_1': - countdownHours = status.value ?? 0; + countdownInSeconds = status.value ?? 0; break; case 'relay_status': relayStatus = status.value ?? 'memory'; @@ -49,6 +48,10 @@ class WaterHeaterStatusModel { } } + final countdownHours = countdownInSeconds ~/ 3600; + final countdownMinutes = + (countdownInSeconds % 3600) ~/ 60; + return WaterHeaterStatusModel( uuid: id, heaterSwitch: heaterSwitch, diff --git a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart index 5974e735..6063a544 100644 --- a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart +++ b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart @@ -75,7 +75,7 @@ class WaterHeaterDeviceControl extends StatelessWidget ), GestureDetector( onTap: () { - context.read().add(const ShowScheduleViewEvent()); + // context.read().add(const ShowScheduleViewEvent()); showDialog( context: context, builder: (ctx) => BlocProvider.value( diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart index 411621eb..3d221789 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart @@ -208,9 +208,11 @@ class BuildScheduleView extends StatelessWidget { Center( child: SizedBox( width: 400, + height: 50, child: Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, children: [ Expanded( child: DefaultButton( @@ -227,12 +229,61 @@ class BuildScheduleView extends StatelessWidget { ), const SizedBox(width: 20), Expanded( - child: DefaultButton( - height: 40, - onPressed: () {}, - backgroundColor: ColorsManager.primaryColor, - child: const Text('Save'), - ), + child: (state.countdownRemaining != null && + state.isActive) + ? 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() + .add(StopScheduleEvent()); + context.read().add( + ToggleWaterHeaterEvent( + deviceId: 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().add( + ToggleWaterHeaterEvent( + deviceId: status.uuid, + code: code, + // value is time in seconds + value: Duration( + hours: state.hours, + minutes: + state.minutes) + .inSeconds, + ), + ); + }, + backgroundColor: + ColorsManager.primaryColor, + child: const Text('Save'), + ), ), ], ), From f2504e5f67882ff40b555f7583864a6bfc953620 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Thu, 19 Sep 2024 09:41:23 +0300 Subject: [PATCH 27/65] add reset event --- .../bloc/device_managment_bloc.dart | 28 +++++++++++++++++++ .../bloc/device_managment_event.dart | 2 ++ 2 files changed, 30 insertions(+) diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart index d57f4685..28a7047a 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart @@ -24,6 +24,7 @@ class DeviceManagementBloc on(_onSearchDevices); on(_onSelectDevice); on(_onResetFilters); + on(_onResetSelectedDevices); } Future _onFetchDevices( @@ -98,6 +99,33 @@ class DeviceManagementBloc )); } + void _onResetSelectedDevices( + ResetSelectedDevices event, Emitter emit) { + _selectedDevices.clear(); + + if (state is DeviceManagementLoaded) { + emit(DeviceManagementLoaded( + devices: _devices, + selectedIndex: _selectedIndex, + onlineCount: _onlineCount, + offlineCount: _offlineCount, + lowBatteryCount: _lowBatteryCount, + selectedDevice: null, + isControlButtonEnabled: false, + )); + } else if (state is DeviceManagementFiltered) { + emit(DeviceManagementFiltered( + filteredDevices: (state as DeviceManagementFiltered).filteredDevices, + selectedIndex: _selectedIndex, + onlineCount: _onlineCount, + offlineCount: _offlineCount, + lowBatteryCount: _lowBatteryCount, + selectedDevice: null, + isControlButtonEnabled: false, + )); + } + } + void _onSelectedFilterChanged( SelectedFilterChanged event, Emitter emit) { _selectedIndex = event.selectedIndex; diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_event.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_event.dart index b2c62ee6..16a2c076 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_managment_event.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_event.dart @@ -52,3 +52,5 @@ class SelectDevice extends DeviceManagementEvent { } class ResetFilters extends DeviceManagementEvent {} + +class ResetSelectedDevices extends DeviceManagementEvent {} From adf553ee9e9327fcfaca62ace2d19f329ecda223 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Thu, 19 Sep 2024 10:18:40 +0300 Subject: [PATCH 28/65] Commented out a line in water heater bloc --- .../water_heater/bloc/water_heater_bloc.dart | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index e095d425..0deb00b7 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -58,8 +58,7 @@ class WaterHeaterBloc extends Bloc { emit: emit, ); - if (success && - (event.code == "countdown_1" || event.code == "switch_inching")) { + if (success && (event.code == "countdown_1" || event.code == "switch_inching")) { final countdownDuration = Duration(seconds: event.value); emit(WaterHeaterScheduleViewState( @@ -109,14 +108,13 @@ class WaterHeaterBloc extends Bloc { return completer.future; } - void _revertValueAndEmit(String deviceId, String code, dynamic oldValue, - Emitter emit) { + void _revertValueAndEmit( + String deviceId, String code, dynamic oldValue, Emitter emit) { _updateLocalValue(code, oldValue, emit); emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); } - void _updateLocalValue( - String code, dynamic value, Emitter emit) { + void _updateLocalValue(String code, dynamic value, Emitter emit) { switch (code) { case 'switch_1': if (value is bool) { @@ -143,13 +141,10 @@ class WaterHeaterBloc extends Bloc { emit(WaterHeaterLoadingState()); try { - final status = - await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = - WaterHeaterStatusModel.fromJson(event.deviceId, status.status); + final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = WaterHeaterStatusModel.fromJson(event.deviceId, status.status); - if (deviceStatus.countdownHours > 0 || - deviceStatus.countdownMinutes > 0) { + if (deviceStatus.countdownHours > 0 || deviceStatus.countdownMinutes > 0) { final remainingDuration = Duration( hours: deviceStatus.countdownHours, minutes: deviceStatus.countdownMinutes, @@ -163,7 +158,7 @@ class WaterHeaterBloc extends Bloc { countdownRemaining: remainingDuration, )); - _startCountdown(remainingDuration, emit); + // _startCountdown(remainingDuration, emit); } else { emit(WaterHeaterScheduleViewState( scheduleMode: deviceStatus.scheduleMode, From 26816b99cd782b5dd283794da9c451f8e58a5cda Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Fri, 20 Sep 2024 00:41:59 +0300 Subject: [PATCH 29/65] push countdown logic --- .../water_heater/bloc/water_heater_bloc.dart | 342 +++++++++++------- .../water_heater/bloc/water_heater_event.dart | 31 +- .../water_heater/bloc/water_heater_state.dart | 60 +-- .../models/water_heater_status_model.dart | 21 +- .../view/water_heater_device_control.dart | 11 +- .../water_heater/widgets/schedual_view.dart | 127 +++++-- 6 files changed, 381 insertions(+), 211 deletions(-) diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index 0deb00b7..c3096216 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -1,3 +1,5 @@ +// water_heater_bloc.dart + import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; @@ -14,143 +16,147 @@ class WaterHeaterBloc extends Bloc { on(_controlWaterHeater); on(_updateScheduleEvent); on(_stopScheduleEvent); + on(_onDecrementCountdown); } late WaterHeaterStatusModel deviceStatus; - Timer? _timer; + Timer? _countdownTimer; FutureOr _updateScheduleEvent( UpdateScheduleEvent event, Emitter emit, ) async { - final currentState = state as WaterHeaterScheduleViewState; + final currentState = state; + if (currentState is WaterHeaterDeviceStatusLoaded) { + final countdownRemaining = + // currentState.isActive == true + // ? currentState.countdownRemaining + // : + Duration(hours: event.hours, minutes: event.minutes); - final countdownRemaining = currentState.isActive - ? currentState.countdownRemaining - : Duration(hours: event.hours, minutes: event.minutes); + emit(currentState.copyWith( + scheduleMode: event.scheduleMode, + hours: countdownRemaining.inHours, + minutes: countdownRemaining.inMinutes % 60, + isActive: currentState.isActive, + countdownRemaining: countdownRemaining, + )); - emit(WaterHeaterScheduleViewState( - scheduleMode: event.scheduleMode, - hours: countdownRemaining!.inHours, - minutes: countdownRemaining.inMinutes % 60, - isActive: currentState.isActive, - countdownRemaining: countdownRemaining, - )); - - if (currentState.isActive) { - _startCountdown(countdownRemaining, emit); + if (!currentState.isActive! && countdownRemaining > Duration.zero) { + _startCountdown(emit, countdownRemaining); + } } } FutureOr _controlWaterHeater( - ToggleWaterHeaterEvent event, Emitter emit) async { - final oldValue = _getValueByCode(event.code); + ToggleWaterHeaterEvent event, + Emitter emit, + ) async { + if (state is WaterHeaterDeviceStatusLoaded) { + final currentState = state as WaterHeaterDeviceStatusLoaded; - _updateLocalValue(event.code, event.value, emit); + final oldValue = _getValueByCode(event.code); - emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); + _updateLocalValue(event.code, event.value); - final success = await _runDebounce( - deviceId: event.deviceId, - code: event.code, - value: event.value, - oldValue: oldValue, - emit: emit, - ); - - if (success && (event.code == "countdown_1" || event.code == "switch_inching")) { - final countdownDuration = Duration(seconds: event.value); - - emit(WaterHeaterScheduleViewState( - scheduleMode: deviceStatus.scheduleMode, - hours: countdownDuration.inHours, - minutes: (countdownDuration.inMinutes % 60), - isActive: true, - countdownRemaining: countdownDuration, + emit(currentState.copyWith( + status: deviceStatus, )); - _startCountdown(countdownDuration, emit); + final success = await _runDebounce( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + ); + + if (success && + (event.code == "countdown_1" || event.code == "switch_inching")) { + final countdownDuration = Duration(seconds: event.value); + + emit(currentState.copyWith( + status: deviceStatus, + scheduleMode: deviceStatus.scheduleMode, + hours: countdownDuration.inHours, + minutes: (countdownDuration.inMinutes % 60), + isActive: true, + countdownRemaining: countdownDuration, + )); + if (countdownDuration.inSeconds > 0) { + _startCountdown(emit, countdownDuration); + } else { + _countdownTimer?.cancel(); + emit(currentState.copyWith( + hours: 0, + minutes: 0, + isActive: false, + countdownRemaining: Duration.zero, + )); + } + } } } - Future _runDebounce({ - required String deviceId, - required String code, - required dynamic value, - required dynamic oldValue, - required Emitter emit, - }) async { - final completer = Completer(); + FutureOr _stopScheduleEvent( + StopScheduleEvent event, + Emitter emit, + ) async { + if (state is WaterHeaterDeviceStatusLoaded) { + final currentState = state as WaterHeaterDeviceStatusLoaded; - if (_timer != null) { - _timer!.cancel(); - } + _countdownTimer?.cancel(); + + deviceStatus = deviceStatus.copyWith( + countdownHours: 0, + countdownMinutes: 0, + scheduleMode: ScheduleModes.countdown, + ); + + emit(currentState.copyWith( + status: deviceStatus, + scheduleMode: ScheduleModes.countdown, + hours: 0, + minutes: 0, + isActive: false, + countdownRemaining: Duration.zero, + )); - _timer = Timer(const Duration(milliseconds: 500), () async { try { final status = await DevicesManagementApi().deviceControl( - deviceId, - Status(code: code, value: value), + event.deviceId, + Status(code: 'countdown_1', value: 0), ); - if (!status) { - _revertValueAndEmit(deviceId, code, oldValue, emit); - completer.complete(false); - } else { - completer.complete(true); + emit(const WaterHeaterFailedState(error: 'Failed to stop schedule.')); } } catch (e) { - _revertValueAndEmit(deviceId, code, oldValue, emit); - completer.complete(false); + emit(WaterHeaterFailedState(error: e.toString())); } - }); - - return completer.future; - } - - void _revertValueAndEmit( - String deviceId, String code, dynamic oldValue, Emitter emit) { - _updateLocalValue(code, oldValue, emit); - emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); - } - - void _updateLocalValue(String code, dynamic value, Emitter emit) { - switch (code) { - case 'switch_1': - if (value is bool) { - deviceStatus = deviceStatus.copyWith(heaterSwitch: value); - } - break; - default: - break; - } - } - - dynamic _getValueByCode(String code) { - switch (code) { - case 'switch_1': - return deviceStatus.heaterSwitch; - - default: - return null; } } FutureOr _fetchWaterHeaterStatus( - WaterHeaterFetchStatusEvent event, Emitter emit) async { + WaterHeaterFetchStatusEvent event, + Emitter emit, + ) async { emit(WaterHeaterLoadingState()); try { - final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = WaterHeaterStatusModel.fromJson(event.deviceId, status.status); + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + WaterHeaterStatusModel.fromJson(event.deviceId, status.status); - if (deviceStatus.countdownHours > 0 || deviceStatus.countdownMinutes > 0) { + if (deviceStatus.countdownHours > 0 || + deviceStatus.countdownMinutes > 0) { final remainingDuration = Duration( hours: deviceStatus.countdownHours, minutes: deviceStatus.countdownMinutes, ); - emit(WaterHeaterScheduleViewState( + emit(WaterHeaterDeviceStatusLoaded( + deviceStatus, scheduleMode: deviceStatus.scheduleMode, hours: deviceStatus.countdownHours, minutes: deviceStatus.countdownMinutes, @@ -158,9 +164,10 @@ class WaterHeaterBloc extends Bloc { countdownRemaining: remainingDuration, )); - // _startCountdown(remainingDuration, emit); + _startCountdown(emit, remainingDuration); } else { - emit(WaterHeaterScheduleViewState( + emit(WaterHeaterDeviceStatusLoaded( + deviceStatus, scheduleMode: deviceStatus.scheduleMode, hours: 0, minutes: 0, @@ -172,55 +179,124 @@ class WaterHeaterBloc extends Bloc { } } - void _startCountdown(Duration duration, Emitter emit) { - _timer?.cancel(); + _onDecrementCountdown( + DecrementCountdownEvent event, + Emitter emit, + ) { + if (state is WaterHeaterDeviceStatusLoaded) { + final currentState = state as WaterHeaterDeviceStatusLoaded; - _timer = Timer.periodic(const Duration(seconds: 1), (timer) { - final state = this.state as WaterHeaterScheduleViewState; - final remaining = state.countdownRemaining! - const Duration(seconds: 1); + if (currentState.countdownRemaining != null && + currentState.countdownRemaining! > Duration.zero) { + final newRemaining = + currentState.countdownRemaining! - const Duration(minutes: 1); - if (remaining.isNegative || remaining == Duration.zero) { - _timer?.cancel(); - emit(WaterHeaterScheduleViewState( - scheduleMode: state.scheduleMode, - hours: 0, - minutes: 0, - isActive: false, - countdownRemaining: Duration.zero, - )); - } else { - emit(WaterHeaterScheduleViewState( - scheduleMode: state.scheduleMode, - hours: remaining.inHours, - minutes: remaining.inMinutes % 60, - isActive: true, - countdownRemaining: remaining, + if (newRemaining <= Duration.zero) { + _countdownTimer?.cancel(); + emit(currentState.copyWith( + hours: 0, + minutes: 0, + isActive: false, + countdownRemaining: Duration.zero, + )); + return; + } + + int totalSeconds = newRemaining.inSeconds; + + int newHours = totalSeconds ~/ 3600; + int newMinutes = (totalSeconds % 3600) ~/ 60; + + emit(currentState.copyWith( + hours: newHours, + minutes: newMinutes, + countdownRemaining: newRemaining, )); } + } + } + + void _startCountdown( + Emitter emit, Duration countdownRemaining) { + _countdownTimer?.cancel(); + + _countdownTimer = Timer.periodic(const Duration(minutes: 1), (timer) { + add(DecrementCountdownEvent()); }); } - FutureOr _stopScheduleEvent( - StopScheduleEvent event, - Emitter emit, - ) { - _timer?.cancel(); - deviceStatus = deviceStatus.copyWith( - countdownHours: 0, - countdownMinutes: 0, - scheduleMode: ScheduleModes.countdown, - ); - emit(const WaterHeaterScheduleViewState( - scheduleMode: ScheduleModes.countdown, - hours: 0, - minutes: 0, - isActive: false, - )); + Future _runDebounce({ + required String deviceId, + required String code, + required dynamic value, + required dynamic oldValue, + required Emitter emit, + }) async { + try { + await Future.delayed(const Duration(milliseconds: 500)); + + final status = await DevicesManagementApi().deviceControl( + deviceId, + Status(code: code, value: value), + ); + + if (!status) { + _revertValue(code, oldValue, emit.call); + return false; + } else { + return true; + } + } catch (e) { + _revertValue(code, oldValue, emit.call); + return false; + } + } + + void _revertValue(String code, dynamic oldValue, + void Function(WaterHeaterState state) emit) { + _updateLocalValue(code, oldValue); + if (state is WaterHeaterDeviceStatusLoaded) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + emit(currentState.copyWith( + status: deviceStatus, + )); + } + } + + void _updateLocalValue(String code, dynamic value) { + switch (code) { + case 'switch_1': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(heaterSwitch: value); + } + break; + case 'countdown_1': + if (value is int) { + deviceStatus = deviceStatus.copyWith( + countdownHours: value ~/ 60, + countdownMinutes: value % 60, + ); + } + break; + default: + break; + } + } + + dynamic _getValueByCode(String code) { + switch (code) { + case 'switch_1': + return deviceStatus.heaterSwitch; + case 'countdown_1': + return deviceStatus.countdownHours * 60 + deviceStatus.countdownMinutes; + default: + return null; + } } @override Future close() { - _timer?.cancel(); + _countdownTimer?.cancel(); return super.close(); } } diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index a0164b3a..1b9c3f11 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -1,3 +1,4 @@ + part of 'water_heater_bloc.dart'; sealed class WaterHeaterEvent extends Equatable { @@ -12,11 +13,14 @@ final class ToggleWaterHeaterEvent extends WaterHeaterEvent { final String deviceId; final String code; - const ToggleWaterHeaterEvent( - {required this.value, required this.deviceId, required this.code}); + const ToggleWaterHeaterEvent({ + required this.value, + required this.deviceId, + required this.code, + }); @override - List get props => [value]; + List get props => [value, deviceId, code]; } final class UpdateScheduleEvent extends WaterHeaterEvent { @@ -24,16 +28,23 @@ final class UpdateScheduleEvent extends WaterHeaterEvent { final int hours; final int minutes; - const UpdateScheduleEvent( - {required this.scheduleMode, required this.hours, required this.minutes}); + const UpdateScheduleEvent({ + required this.scheduleMode, + required this.hours, + required this.minutes, + }); @override List get props => [scheduleMode, hours, minutes]; } -final class StopScheduleEvent extends WaterHeaterEvent {} +final class StopScheduleEvent extends WaterHeaterEvent { + final String deviceId; -class WaterHeaterFetchStatusEvent extends WaterHeaterEvent { + const StopScheduleEvent(this.deviceId); +} + +final class WaterHeaterFetchStatusEvent extends WaterHeaterEvent { final String deviceId; const WaterHeaterFetchStatusEvent(this.deviceId); @@ -42,7 +53,7 @@ class WaterHeaterFetchStatusEvent extends WaterHeaterEvent { List get props => [deviceId]; } -class WaterHeaterFetchBatchStatusEvent extends WaterHeaterEvent { +final class WaterHeaterFetchBatchStatusEvent extends WaterHeaterEvent { final String deviceId; const WaterHeaterFetchBatchStatusEvent(this.deviceId); @@ -51,6 +62,4 @@ class WaterHeaterFetchBatchStatusEvent extends WaterHeaterEvent { List get props => [deviceId]; } -// class ShowScheduleViewEvent extends WaterHeaterEvent { -// const ShowScheduleViewEvent(); -// } +final class DecrementCountdownEvent extends WaterHeaterEvent {} diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart index 2dc45367..092cd90e 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart @@ -1,6 +1,6 @@ -part of 'water_heater_bloc.dart'; +// water_heater_state.dart -enum ScheduleType { countdown, schedule, circulate, inching } +part of 'water_heater_bloc.dart'; sealed class WaterHeaterState extends Equatable { const WaterHeaterState(); @@ -15,11 +15,43 @@ final class WaterHeaterLoadingState extends WaterHeaterState {} final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { final WaterHeaterStatusModel status; + final ScheduleModes? scheduleMode; + final int? hours; + final int? minutes; + final bool? isActive; + final Duration? countdownRemaining; - const WaterHeaterDeviceStatusLoaded(this.status); + const WaterHeaterDeviceStatusLoaded( + this.status, { + this.scheduleMode, + this.hours, + this.minutes, + this.isActive, + this.countdownRemaining, + }); @override - List get props => [status]; + List get props => + [status, scheduleMode, hours, minutes, isActive, countdownRemaining]; + + /// Creates a new instance with updated fields. + WaterHeaterDeviceStatusLoaded copyWith({ + WaterHeaterStatusModel? status, + ScheduleModes? scheduleMode, + int? hours, + int? minutes, + bool? isActive, + Duration? countdownRemaining, + }) { + return WaterHeaterDeviceStatusLoaded( + status ?? this.status, + scheduleMode: scheduleMode ?? this.scheduleMode, + hours: hours ?? this.hours, + minutes: minutes ?? this.minutes, + isActive: isActive ?? this.isActive, + countdownRemaining: countdownRemaining ?? this.countdownRemaining, + ); + } } final class WaterHeaterFailedState extends WaterHeaterState { @@ -39,23 +71,3 @@ final class WaterHeaterBatchFailedState extends WaterHeaterState { @override List get props => [error]; } - -class WaterHeaterScheduleViewState extends WaterHeaterState { - final ScheduleModes scheduleMode; - final int hours; - final int minutes; - final bool isActive; - final Duration? countdownRemaining; - - const WaterHeaterScheduleViewState({ - required this.scheduleMode, - required this.hours, - required this.minutes, - required this.isActive, - this.countdownRemaining, - }); - - @override - List get props => - [scheduleMode, hours, minutes, isActive, countdownRemaining]; -} diff --git a/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart b/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart index fd16df50..2eebefa9 100644 --- a/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart +++ b/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart @@ -1,8 +1,9 @@ +import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; enum ScheduleModes { countdown, schedule, circulate, inching } -class WaterHeaterStatusModel { +class WaterHeaterStatusModel extends Equatable { final String uuid; final bool heaterSwitch; final int countdownHours; @@ -34,7 +35,7 @@ class WaterHeaterStatusModel { heaterSwitch = status.value ?? false; break; case 'countdown_1': - countdownInSeconds = status.value ?? 0; + countdownInSeconds = status.value ?? 0; break; case 'relay_status': relayStatus = status.value ?? 'memory'; @@ -48,9 +49,8 @@ class WaterHeaterStatusModel { } } - final countdownHours = countdownInSeconds ~/ 3600; - final countdownMinutes = - (countdownInSeconds % 3600) ~/ 60; + final countdownHours = countdownInSeconds ~/ 3600; + final countdownMinutes = (countdownInSeconds % 3600) ~/ 60; return WaterHeaterStatusModel( uuid: id, @@ -97,4 +97,15 @@ class WaterHeaterStatusModel { return ScheduleModes.countdown; } } + + @override + List get props => [ + uuid, + heaterSwitch, + countdownHours, + countdownMinutes, + scheduleMode, + relayStatus, + cycleTiming, + ]; } diff --git a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart index 6063a544..e30303ac 100644 --- a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart +++ b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart @@ -29,10 +29,12 @@ class WaterHeaterDeviceControl extends StatelessWidget return const Center(child: CircularProgressIndicator()); } else if (state is WaterHeaterDeviceStatusLoaded) { return _buildStatusControls(context, state.status); - } else if (state is WaterHeaterScheduleViewState) { - final status = context.read().deviceStatus; - return _buildStatusControls(context, status); - } else if (state is WaterHeaterFailedState || + } + // else if (state is WaterHeaterScheduleViewState) { + // final status = context.read().deviceStatus; + // return _buildStatusControls(context, status); + // } + else if (state is WaterHeaterFailedState || state is WaterHeaterBatchFailedState) { return const Center(child: Text('Error fetching status')); } else { @@ -75,7 +77,6 @@ class WaterHeaterDeviceControl extends StatelessWidget ), GestureDetector( onTap: () { - // context.read().add(const ShowScheduleViewEvent()); showDialog( context: context, builder: (ctx) => BlocProvider.value( diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart index 3d221789..5010eaf4 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart @@ -7,11 +7,63 @@ import 'package:syncrow_web/pages/device_managment/water_heater/models/water_hea import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -class BuildScheduleView extends StatelessWidget { +class BuildScheduleView extends StatefulWidget { const BuildScheduleView({super.key, required this.status}); final WaterHeaterStatusModel status; + @override + _BuildScheduleViewState createState() => _BuildScheduleViewState(); +} + +class _BuildScheduleViewState extends State { + // late FixedExtentScrollController hoursController; + // late FixedExtentScrollController minutesController; + + // @override + // void initState() { + // super.initState(); + // _initializeControllers(); + // } + + // @override + // void didUpdateWidget(covariant BuildScheduleView oldWidget) { + // super.didUpdateWidget(oldWidget); + // final state = context.read().state; + // if (state is WaterHeaterDeviceStatusLoaded) { + // _initializeControllers(); + // } + // } + + // void _initializeControllers() { + // final state = context.read().state; + // if (state is WaterHeaterDeviceStatusLoaded) { + // hoursController = + // FixedExtentScrollController(initialItem: state.hours ?? 0); + // minutesController = + // FixedExtentScrollController(initialItem: state.minutes ?? 0); + // } else { + // hoursController = FixedExtentScrollController(initialItem: 0); + // minutesController = FixedExtentScrollController(initialItem: 0); + // } + // } + + // void _updateControllers(int hours, int minutes) { + // if (hoursController.hasClients) { + // hoursController.jumpToItem(hours); + // } + // if (minutesController.hasClients) { + // minutesController.jumpToItem(minutes); + // } + // } + + // @override + // void dispose() { + // hoursController.dispose(); + // minutesController.dispose(); + // super.dispose(); + // } + @override Widget build(BuildContext context) { return Dialog( @@ -27,7 +79,8 @@ class BuildScheduleView extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 40.0, vertical: 20), child: BlocBuilder( builder: (context, state) { - if (state is WaterHeaterScheduleViewState) { + if (state is WaterHeaterDeviceStatusLoaded) { + //_updateControllers(state.hours ?? 0, state.minutes ?? 0); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -75,9 +128,7 @@ class BuildScheduleView extends StatelessWidget { color: ColorsManager.grayColor, ), ), - const SizedBox( - height: 4, - ), + const SizedBox(height: 4), SizedBox( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, @@ -101,8 +152,8 @@ class BuildScheduleView extends StatelessWidget { .read() .add(UpdateScheduleEvent( scheduleMode: value, - hours: state.hours, - minutes: state.minutes, + hours: state.hours ?? 0, + minutes: state.minutes ?? 0, )); } }, @@ -128,8 +179,8 @@ class BuildScheduleView extends StatelessWidget { .read() .add(UpdateScheduleEvent( scheduleMode: value, - hours: state.hours, - minutes: state.minutes, + hours: state.hours ?? 0, + minutes: state.minutes ?? 0, )); } }, @@ -154,8 +205,8 @@ class BuildScheduleView extends StatelessWidget { .read() .add(UpdateScheduleEvent( scheduleMode: value, - hours: state.hours, - minutes: state.minutes, + hours: state.hours ?? 0, + minutes: state.minutes ?? 0, )); } }, @@ -180,8 +231,8 @@ class BuildScheduleView extends StatelessWidget { .read() .add(UpdateScheduleEvent( scheduleMode: value, - hours: state.hours, - minutes: state.minutes, + hours: state.hours ?? 0, + minutes: state.minutes ?? 0, )); } }, @@ -230,7 +281,7 @@ class BuildScheduleView extends StatelessWidget { const SizedBox(width: 20), Expanded( child: (state.countdownRemaining != null && - state.isActive) + state.isActive == true) ? DefaultButton( height: 40, onPressed: () { @@ -242,12 +293,13 @@ class BuildScheduleView extends StatelessWidget { ScheduleModes.inching) { code = 'switch_inching'; } - context - .read() - .add(StopScheduleEvent()); + context.read().add( + StopScheduleEvent( + widget.status.uuid)); context.read().add( ToggleWaterHeaterEvent( - deviceId: status.uuid, + deviceId: + widget.status.uuid, code: code, value: 0, ), @@ -269,13 +321,16 @@ class BuildScheduleView extends StatelessWidget { } context.read().add( ToggleWaterHeaterEvent( - deviceId: status.uuid, + deviceId: + widget.status.uuid, code: code, - // value is time in seconds value: Duration( - hours: state.hours, + hours: + state.hours ?? + 0, minutes: - state.minutes) + state.minutes ?? + 0) .inSeconds, ), ); @@ -303,7 +358,7 @@ class BuildScheduleView extends StatelessWidget { } Row _hourMinutesWheel( - WaterHeaterScheduleViewState state, BuildContext context) { + WaterHeaterDeviceStatusLoaded state, BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -320,16 +375,19 @@ class BuildScheduleView extends StatelessWidget { borderRadius: BorderRadius.circular(8), ), child: ListWheelScrollView.useDelegate( - controller: - FixedExtentScrollController(initialItem: state.hours), + key: ValueKey('hours_${state.hours}'), + controller: FixedExtentScrollController( + initialItem: state.hours ?? 0, + ), itemExtent: 40.0, - physics: FixedExtentScrollPhysics(), + physics: const FixedExtentScrollPhysics(), onSelectedItemChanged: (int value) { context.read().add( UpdateScheduleEvent( - scheduleMode: state.scheduleMode, + scheduleMode: + state.scheduleMode ?? ScheduleModes.countdown, hours: value, - minutes: state.minutes, + minutes: state.minutes ?? 0, ), ); }, @@ -370,15 +428,18 @@ class BuildScheduleView extends StatelessWidget { borderRadius: BorderRadius.circular(8), ), child: ListWheelScrollView.useDelegate( - controller: - FixedExtentScrollController(initialItem: state.minutes), + key: ValueKey('minutes_${state.minutes}'), + controller: FixedExtentScrollController( + initialItem: state.minutes ?? 0, + ), itemExtent: 40.0, - physics: FixedExtentScrollPhysics(), + physics: const FixedExtentScrollPhysics(), onSelectedItemChanged: (int value) { context.read().add( UpdateScheduleEvent( - scheduleMode: state.scheduleMode, - hours: state.hours, + scheduleMode: + state.scheduleMode ?? ScheduleModes.countdown, + hours: state.hours ?? 0, minutes: value, ), ); From 921ccf0132a96b888176066385290d1ad6657672 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Fri, 20 Sep 2024 02:27:07 +0300 Subject: [PATCH 30/65] push schedule basic design and bloc manegment --- .../water_heater/bloc/water_heater_bloc.dart | 54 +- .../water_heater/bloc/water_heater_event.dart | 42 +- .../water_heater/bloc/water_heater_state.dart | 32 +- .../water_heater/widgets/schedual_view.dart | 1036 ++++++++++------- 4 files changed, 725 insertions(+), 439 deletions(-) diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index c3096216..fcf52732 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -3,6 +3,7 @@ import 'dart:async'; 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/water_heater_status_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; @@ -17,6 +18,9 @@ class WaterHeaterBloc extends Bloc { on(_updateScheduleEvent); on(_stopScheduleEvent); on(_onDecrementCountdown); + on(_onAddSchedule); + on(_onDeleteSchedule); + on(_onUpdateSchedule); } late WaterHeaterStatusModel deviceStatus; @@ -179,7 +183,7 @@ class WaterHeaterBloc extends Bloc { } } - _onDecrementCountdown( + _onDecrementCountdown( DecrementCountdownEvent event, Emitter emit, ) { @@ -294,6 +298,54 @@ class WaterHeaterBloc extends Bloc { } } + FutureOr _onAddSchedule( + AddScheduleEvent event, + Emitter emit, + ) { + if (state is WaterHeaterDeviceStatusLoaded) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + final newSchedule = ScheduleEntry( + selectedDays: event.selectedDays, + time: event.time, + functionOn: event.functionOn, + ); + final updatedSchedules = List.from(currentState.schedules) + ..add(newSchedule); + + emit(currentState.copyWith(schedules: updatedSchedules)); + } + } + + FutureOr _onDeleteSchedule( + DeleteScheduleEvent event, + Emitter emit, + ) { + if (state is WaterHeaterDeviceStatusLoaded) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + final updatedSchedules = List.from(currentState.schedules) + ..removeAt(event.index); + + emit(currentState.copyWith(schedules: updatedSchedules)); + } + } + + FutureOr _onUpdateSchedule( + UpdateScheduleEntryEvent event, + Emitter emit, + ) { + if (state is WaterHeaterDeviceStatusLoaded) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + final updatedSchedules = List.from(currentState.schedules); + updatedSchedules[event.index] = ScheduleEntry( + selectedDays: event.selectedDays, + time: event.time, + functionOn: event.functionOn, + ); + + emit(currentState.copyWith(schedules: updatedSchedules)); + } + } + @override Future close() { _countdownTimer?.cancel(); diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index 1b9c3f11..360f4a4f 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -1,4 +1,3 @@ - part of 'water_heater_bloc.dart'; sealed class WaterHeaterEvent extends Equatable { @@ -63,3 +62,44 @@ final class WaterHeaterFetchBatchStatusEvent extends WaterHeaterEvent { } final class DecrementCountdownEvent extends WaterHeaterEvent {} + +final class AddScheduleEvent extends WaterHeaterEvent { + final List selectedDays; + final TimeOfDay time; + final bool functionOn; + + const AddScheduleEvent({ + required this.selectedDays, + required this.time, + required this.functionOn, + }); + + @override + List get props => [selectedDays, time, functionOn]; +} + +final class DeleteScheduleEvent extends WaterHeaterEvent { + final int index; + + const DeleteScheduleEvent(this.index); + + @override + List get props => [index]; +} + +final class UpdateScheduleEntryEvent extends WaterHeaterEvent { + final int index; + final List selectedDays; + final TimeOfDay time; + final bool functionOn; + + const UpdateScheduleEntryEvent({ + required this.index, + required this.selectedDays, + required this.time, + required this.functionOn, + }); + + @override + List get props => [index, selectedDays, time, functionOn]; +} diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart index 092cd90e..95c68f24 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart @@ -20,6 +20,7 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { final int? minutes; final bool? isActive; final Duration? countdownRemaining; + final List schedules; const WaterHeaterDeviceStatusLoaded( this.status, { @@ -28,13 +29,20 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { this.minutes, this.isActive, this.countdownRemaining, + this.schedules = const [], }); @override - List get props => - [status, scheduleMode, hours, minutes, isActive, countdownRemaining]; + List get props => [ + status, + scheduleMode, + hours, + minutes, + isActive, + countdownRemaining, + schedules, + ]; - /// Creates a new instance with updated fields. WaterHeaterDeviceStatusLoaded copyWith({ WaterHeaterStatusModel? status, ScheduleModes? scheduleMode, @@ -42,6 +50,7 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { int? minutes, bool? isActive, Duration? countdownRemaining, + List? schedules, }) { return WaterHeaterDeviceStatusLoaded( status ?? this.status, @@ -50,10 +59,27 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { minutes: minutes ?? this.minutes, isActive: isActive ?? this.isActive, countdownRemaining: countdownRemaining ?? this.countdownRemaining, + schedules: schedules ?? this.schedules, ); } } +class ScheduleEntry { + final List selectedDays; + final TimeOfDay time; + final bool functionOn; + + ScheduleEntry({ + required this.selectedDays, + required this.time, + required this.functionOn, + }); + + @override + String toString() => + 'ScheduleEntry(selectedDays: $selectedDays, time: $time, functionOn: $functionOn)'; +} + final class WaterHeaterFailedState extends WaterHeaterState { final String error; diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart index 5010eaf4..74cdb94b 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; @@ -17,339 +16,46 @@ class BuildScheduleView extends StatefulWidget { } class _BuildScheduleViewState extends State { - // late FixedExtentScrollController hoursController; - // late FixedExtentScrollController minutesController; - - // @override - // void initState() { - // super.initState(); - // _initializeControllers(); - // } - - // @override - // void didUpdateWidget(covariant BuildScheduleView oldWidget) { - // super.didUpdateWidget(oldWidget); - // final state = context.read().state; - // if (state is WaterHeaterDeviceStatusLoaded) { - // _initializeControllers(); - // } - // } - - // void _initializeControllers() { - // final state = context.read().state; - // if (state is WaterHeaterDeviceStatusLoaded) { - // hoursController = - // FixedExtentScrollController(initialItem: state.hours ?? 0); - // minutesController = - // FixedExtentScrollController(initialItem: state.minutes ?? 0); - // } else { - // hoursController = FixedExtentScrollController(initialItem: 0); - // minutesController = FixedExtentScrollController(initialItem: 0); - // } - // } - - // void _updateControllers(int hours, int minutes) { - // if (hoursController.hasClients) { - // hoursController.jumpToItem(hours); - // } - // if (minutesController.hasClients) { - // minutesController.jumpToItem(minutes); - // } - // } - - // @override - // void dispose() { - // hoursController.dispose(); - // minutesController.dispose(); - // super.dispose(); - // } - @override Widget build(BuildContext context) { - return 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( - builder: (context, state) { - if (state is WaterHeaterDeviceStatusLoaded) { - //_updateControllers(state.hours ?? 0, state.minutes ?? 0); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 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(); - }, - ), - ), - ], - ), - const SizedBox(height: 20), - Text( - 'Type:', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), - const SizedBox(height: 4), - SizedBox( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Flexible( - child: ListTile( - contentPadding: EdgeInsets.zero, - title: Text( - 'Countdown', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.blackColor, - ), - ), - leading: Radio( - value: ScheduleModes.countdown, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - context - .read() - .add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.hours ?? 0, - minutes: state.minutes ?? 0, - )); - } - }, - ), - ), - ), - Flexible( - child: ListTile( - contentPadding: EdgeInsets.zero, - title: Text( - 'Schedule', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.blackColor, - ), - ), - leading: Radio( - value: ScheduleModes.schedule, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - context - .read() - .add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.hours ?? 0, - minutes: state.minutes ?? 0, - )); - } - }, - ), - ), - ), - Flexible( - child: ListTile( - title: Text( - 'Circulate', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.blackColor, - ), - ), - leading: Radio( - value: ScheduleModes.circulate, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - context - .read() - .add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.hours ?? 0, - minutes: state.minutes ?? 0, - )); - } - }, - ), - ), - ), - Flexible( - child: ListTile( - title: Text( - 'Inching', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.blackColor, - ), - ), - leading: Radio( - value: ScheduleModes.inching, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - context - .read() - .add(UpdateScheduleEvent( - scheduleMode: value, - hours: state.hours ?? 0, - minutes: state.minutes ?? 0, - )); - } - }, - ), - ), - ), - ], - ), - ), - const SizedBox(height: 20), - if (state.scheduleMode == ScheduleModes.countdown || - state.scheduleMode == ScheduleModes.inching) ...[ - Text( - 'Countdown:', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), - const SizedBox(height: 4), - _hourMinutesWheel(state, context) + final bloc = BlocProvider.of(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( + 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), ], - const SizedBox(height: 20), - Center( - child: SizedBox( - width: 400, - height: 50, - child: Center( - 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().add( - StopScheduleEvent( - widget.status.uuid)); - context.read().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().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'), - ), - ), - ], - ), - ), - ), - ), - ], - ); - } - return const SizedBox(); - }, + ); + } + return const SizedBox(); + }, + ), ), ), ), @@ -357,117 +63,579 @@ class _BuildScheduleViewState extends State { ); } - Row _hourMinutesWheel( - WaterHeaterDeviceStatusLoaded state, BuildContext context) { + Row _scheduleHeader(BuildContext context) { return Row( - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - // Hours Picker - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - height: 50, - width: 80, - padding: const EdgeInsets.symmetric(horizontal: 16), - decoration: BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.circular(8), - ), - child: ListWheelScrollView.useDelegate( - key: ValueKey('hours_${state.hours}'), - controller: FixedExtentScrollController( - initialItem: state.hours ?? 0, - ), - itemExtent: 40.0, - physics: const FixedExtentScrollPhysics(), - onSelectedItemChanged: (int value) { - context.read().add( - UpdateScheduleEvent( - scheduleMode: - state.scheduleMode ?? ScheduleModes.countdown, - hours: value, - minutes: state.minutes ?? 0, - ), - ); - }, - childDelegate: ListWheelChildBuilderDelegate( - builder: (context, index) { - return Center( - child: Text( - index.toString().padLeft(2, '0'), - style: const TextStyle(fontSize: 24), - ), - ); - }, - childCount: 24, - ), - ), - ), - const SizedBox(height: 8), - const Text( - 'h', - style: TextStyle( - color: ColorsManager.grayColor, - fontSize: 18, - ), - ), - ], + const SizedBox(), + Text( + 'Scheduling', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 22, + color: ColorsManager.dialogBlueTitle, + ), ), - const SizedBox(width: 10), - // Minutes Picker + 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( - mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Container( - height: 50, - width: 80, - padding: const EdgeInsets.symmetric(horizontal: 16), - decoration: BoxDecoration( - color: ColorsManager.boxColor, - borderRadius: BorderRadius.circular(8), - ), - child: ListWheelScrollView.useDelegate( - key: ValueKey('minutes_${state.minutes}'), - controller: FixedExtentScrollController( - initialItem: state.minutes ?? 0, - ), - itemExtent: 40.0, - physics: const FixedExtentScrollPhysics(), - onSelectedItemChanged: (int value) { - context.read().add( - UpdateScheduleEvent( - scheduleMode: - state.scheduleMode ?? ScheduleModes.countdown, - hours: state.hours ?? 0, - minutes: value, - ), - ); - }, - childDelegate: ListWheelChildBuilderDelegate( - builder: (context, index) { - return Center( - child: Text( - index.toString().padLeft(2, '0'), - style: const TextStyle(fontSize: 24), - ), - ); - }, - childCount: 60, - ), - ), - ), - const SizedBox(height: 8), - const Text( - 'm', - style: TextStyle( - color: ColorsManager.grayColor, - fontSize: 18, - ), - ), + _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( + value: mode, + groupValue: state.scheduleMode, + onChanged: (ScheduleModes? value) { + if (value != null) { + context.read().add(UpdateScheduleEvent( + scheduleMode: value, + hours: state.hours ?? 0, + minutes: state.minutes ?? 0, + )); + } + }, + ), + ), + ); + } + + Widget _buildScheduleManagementUI(WaterHeaterDeviceStatusLoaded state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ElevatedButton( + onPressed: () => _showAddScheduleDialog(context), + child: Text('+ Add new schedule'), + ), + const SizedBox(height: 20), + _buildScheduleTable(state), + ], + ); + } + + Widget _buildScheduleTable(WaterHeaterDeviceStatusLoaded state) { + return Table( + border: TableBorder.all(color: Colors.grey), + children: [ + TableRow( + children: [ + _buildTableHeader('Active'), + _buildTableHeader('Days'), + _buildTableHeader('Time'), + _buildTableHeader('Function'), + _buildTableHeader('Action'), + ], + ), + if (state.schedules.isEmpty) + TableRow( + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'No schedules added yet', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + ), + ), + const SizedBox(), + const SizedBox(), + const SizedBox(), + const SizedBox(), + ], + ), + 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 + ? Icon(Icons.radio_button_checked) + : 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( + onPressed: () { + _showEditScheduleDialog(context, schedule, index); + }, + child: Text( + 'Edit', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blueColor), + ), + ), + TextButton( + onPressed: () { + context + .read() + .add(DeleteScheduleEvent(index)); + }, + child: Text( + 'Delete', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blueColor), + ), + ), + ], + ), + ), + ], + ); + } + + String _getSelectedDays(List selectedDays) { + final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + List selectedDaysStr = []; + for (int i = 0; i < selectedDays.length; i++) { + if (selectedDays[i]) { + selectedDaysStr.add(days[i]); + } + } + return selectedDaysStr.join(', '); + } + + void _showEditScheduleDialog( + BuildContext context, ScheduleEntry schedule, int index) { + List selectedDays = List.from(schedule.selectedDays); + TimeOfDay selectedTime = schedule.time; + bool isOn = schedule.functionOn; + + showDialog( + context: context, + builder: (ctx) { + return StatefulBuilder( + builder: (context, setState) { + return AlertDialog( + title: const Text('Edit Schedule'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton( + onPressed: () async { + TimeOfDay? time = await showTimePicker( + context: context, + initialTime: selectedTime, + ); + if (time != null) { + setState(() { + selectedTime = time; + }); + } + }, + child: Text(selectedTime.format(context)), + ), + // Same checkboxes and function on/off logic as before + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + context.read().add( + UpdateScheduleEntryEvent( + index: index, + selectedDays: selectedDays, + time: selectedTime, + functionOn: isOn, + ), + ); + Navigator.pop(context); + }, + child: const Text('Save'), + ), + ], + ); + }, + ); + }, + ); + } + + Widget _buildTableHeader(String label) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + label, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ); + } + + void _showAddScheduleDialog(BuildContext context) { + showDialog( + context: context, + builder: (ctx) { + List selectedDays = [ + false, + false, + false, + false, + false, + false, + false + ]; // Mon - Sun + TimeOfDay? selectedTime; + bool isOn = false; + + return BlocProvider.value( + value: BlocProvider.of(context), + child: StatefulBuilder( + builder: (ctx, setState) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20)), + title: const Text('Scheduling'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton( + onPressed: () async { + TimeOfDay? time = await showTimePicker( + context: context, + initialTime: TimeOfDay.now(), + ); + if (time != null) { + setState(() { + selectedTime = time; + }); + } + }, + child: Text( + selectedTime == null + ? 'Time' + : '${selectedTime!.format(context)}', + ), + ), + const SizedBox(height: 10), + Row( + children: [ + _buildDayCheckbox(setState, 'Mon', 0, selectedDays), + _buildDayCheckbox(setState, 'Tue', 1, selectedDays), + _buildDayCheckbox(setState, 'Wed', 2, selectedDays), + _buildDayCheckbox(setState, 'Thu', 3, selectedDays), + _buildDayCheckbox(setState, 'Fri', 4, selectedDays), + _buildDayCheckbox(setState, 'Sat', 5, selectedDays), + _buildDayCheckbox(setState, 'Sun', 6, selectedDays), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + const Text('Function:'), + Radio( + value: true, + groupValue: isOn, + onChanged: (bool? value) { + setState(() { + isOn = value!; + }); + }, + ), + const Text('On'), + Radio( + value: false, + groupValue: isOn, + onChanged: (bool? value) { + setState(() { + isOn = value!; + }); + }, + ), + const Text('Off'), + ], + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + // Dispatch Bloc Event to Add Schedule + if (selectedTime != null) { + context.read().add(AddScheduleEvent( + time: selectedTime!, + selectedDays: selectedDays, + functionOn: isOn, + )); + Navigator.pop(context); // Close the dialog + } + }, + child: const Text('Save'), + ), + ], + ); + }, + ), + ); + }, + ); + } + + Widget _buildDayCheckbox(void Function(void Function()) setState, String day, + int index, List selectedDays) { + return Row( + children: [ + Checkbox( + value: selectedDays[index], + onChanged: (bool? value) { + setState(() { + selectedDays[index] = value!; + }); + }, + ), + Text(day), + ], + ); + } + + 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() + .add(StopScheduleEvent(widget.status.uuid)); + context.read().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().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 _buildCountDownAngInchingView( + BuildContext context, WaterHeaterDeviceStatusLoaded state) { + return [ + Text( + 'Countdown:', + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + const SizedBox(height: 4), + _hourMinutesWheel(state, context) + ]; + } + + Row _hourMinutesWheel( + WaterHeaterDeviceStatusLoaded state, BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _buildPickerColumn(context, 'h', state.hours ?? 0, 24, (value) { + context.read().add(UpdateScheduleEvent( + scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, + hours: value, + minutes: state.minutes ?? 0, + )); + }), + const SizedBox(width: 10), + _buildPickerColumn(context, 'm', state.minutes ?? 0, 60, (value) { + context.read().add(UpdateScheduleEvent( + scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, + hours: state.hours ?? 0, + minutes: value, + )); + }), + ], + ); + } + + Widget _buildPickerColumn(BuildContext context, String label, + int initialValue, int itemCount, ValueChanged onSelected) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 50, + 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: const TextStyle(fontSize: 24), + ), + ); + }, + childCount: itemCount, + ), + ), + ), + const SizedBox(height: 8), + Text( + label, + style: const TextStyle( + color: ColorsManager.grayColor, + fontSize: 18, + ), + ), + ], + ); + } } From 66f45721e51036d42b749a02c1a13c4b9a51184d Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 22 Sep 2024 00:16:44 +0300 Subject: [PATCH 31/65] working on schedual table --- lib/pages/common/buttons/default_button.dart | 4 + .../water_heater/widgets/schedual_view.dart | 222 +++++++++++++----- lib/utils/theme/theme.dart | 7 +- 3 files changed, 165 insertions(+), 68 deletions(-) diff --git a/lib/pages/common/buttons/default_button.dart b/lib/pages/common/buttons/default_button.dart index 5aa506f8..77eb9bd7 100644 --- a/lib/pages/common/buttons/default_button.dart +++ b/lib/pages/common/buttons/default_button.dart @@ -17,6 +17,7 @@ class DefaultButton extends StatelessWidget { this.borderRadius, this.height, this.padding, + this.borderColor, }); final void Function()? onPressed; final Widget child; @@ -31,6 +32,8 @@ class DefaultButton extends StatelessWidget { final ButtonStyle? customButtonStyle; final Color? backgroundColor; final Color? foregroundColor; + final Color? borderColor; + @override Widget build(BuildContext context) { return ElevatedButton( @@ -61,6 +64,7 @@ class DefaultButton extends StatelessWidget { }), shape: MaterialStateProperty.all( RoundedRectangleBorder( + side: BorderSide(color: borderColor ?? Colors.transparent), borderRadius: BorderRadius.circular(borderRadius ?? 20), ), ), diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart index 74cdb94b..125a9483 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart @@ -163,9 +163,27 @@ class _BuildScheduleViewState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - ElevatedButton( - onPressed: () => _showAddScheduleDialog(context), - child: Text('+ Add new schedule'), + SizedBox( + width: 170, + height: 40, + child: DefaultButton( + borderColor: ColorsManager.boxColor, + padding: 2, + backgroundColor: ColorsManager.graysColor, + borderRadius: 15, + onPressed: () => _showAddScheduleDialog(context), + 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), @@ -219,8 +237,8 @@ class _BuildScheduleViewState extends State { children: [ Center( child: schedule.functionOn - ? Icon(Icons.radio_button_checked) - : Icon(Icons.radio_button_unchecked)), + ? const Icon(Icons.radio_button_checked) + : 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')), @@ -230,7 +248,8 @@ class _BuildScheduleViewState extends State { children: [ TextButton( onPressed: () { - _showEditScheduleDialog(context, schedule, index); + _showAddScheduleDialog(context); + // _showEditScheduleDialog(context, schedule, index); }, child: Text( 'Edit', @@ -298,7 +317,6 @@ class _BuildScheduleViewState extends State { }, child: Text(selectedTime.format(context)), ), - // Same checkboxes and function on/off logic as before ], ), actions: [ @@ -340,7 +358,8 @@ class _BuildScheduleViewState extends State { ); } - void _showAddScheduleDialog(BuildContext context) { + void _showAddScheduleDialog( + BuildContext context) { showDialog( context: context, builder: (ctx) { @@ -352,10 +371,10 @@ class _BuildScheduleViewState extends State { false, false, false - ]; // Mon - Sun + ]; + TimeOfDay? selectedTime; bool isOn = false; - return BlocProvider.value( value: BlocProvider.of(context), child: StatefulBuilder( @@ -363,44 +382,88 @@ class _BuildScheduleViewState extends State { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20)), - title: const Text('Scheduling'), content: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - ElevatedButton( - onPressed: () async { - TimeOfDay? time = await showTimePicker( - context: context, - initialTime: TimeOfDay.now(), - ); - if (time != null) { - setState(() { - selectedTime = time; - }); - } - }, - child: Text( - selectedTime == null - ? 'Time' - : '${selectedTime!.format(context)}', - ), - ), - const SizedBox(height: 10), Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _buildDayCheckbox(setState, 'Mon', 0, selectedDays), - _buildDayCheckbox(setState, 'Tue', 1, selectedDays), - _buildDayCheckbox(setState, 'Wed', 2, selectedDays), - _buildDayCheckbox(setState, 'Thu', 3, selectedDays), - _buildDayCheckbox(setState, 'Fri', 4, selectedDays), - _buildDayCheckbox(setState, 'Sat', 5, selectedDays), - _buildDayCheckbox(setState, 'Sun', 6, selectedDays), + const SizedBox(), + Text( + 'Scheduling', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(), ], ), - const SizedBox(height: 10), + 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: TimeOfDay.now(), + ); + if (time != null) { + setState(() { + selectedTime = time; + }); + } + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + selectedTime == null + ? 'Time' + : 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), Row( children: [ - const Text('Function:'), + _buildDayCheckbox(setState, 'Mon.', 0, selectedDays), + _buildDayCheckbox(setState, 'Tue.', 1, selectedDays), + _buildDayCheckbox(setState, 'Wed.', 2, selectedDays), + _buildDayCheckbox(setState, 'Thu.', 3, selectedDays), + _buildDayCheckbox(setState, 'Fri.', 4, selectedDays), + _buildDayCheckbox(setState, 'Sat.', 5, selectedDays), + _buildDayCheckbox(setState, 'Sun.', 6, selectedDays), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + Text( + 'Function:', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.grayColor), + ), + const SizedBox( + width: 10, + ), Radio( value: true, groupValue: isOn, @@ -411,6 +474,9 @@ class _BuildScheduleViewState extends State { }, ), const Text('On'), + const SizedBox( + width: 10, + ), Radio( value: false, groupValue: isOn, @@ -426,25 +492,37 @@ class _BuildScheduleViewState extends State { ], ), actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Cancel'), + SizedBox( + width: 200, + child: DefaultButton( + height: 40, + onPressed: () { + Navigator.pop(context); + }, + backgroundColor: ColorsManager.boxColor, + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium, + ), + ), ), - TextButton( - onPressed: () { - // Dispatch Bloc Event to Add Schedule - if (selectedTime != null) { - context.read().add(AddScheduleEvent( - time: selectedTime!, - selectedDays: selectedDays, - functionOn: isOn, - )); - Navigator.pop(context); // Close the dialog - } - }, - child: const Text('Save'), + SizedBox( + width: 200, + child: DefaultButton( + height: 40, + onPressed: () { + if (selectedTime != null) { + context.read().add(AddScheduleEvent( + time: selectedTime!, + selectedDays: selectedDays, + functionOn: isOn, + )); + Navigator.pop(context); + } + }, + backgroundColor: ColorsManager.primaryColor, + child: const Text('Save'), + ), ), ], ); @@ -556,21 +634,31 @@ class _BuildScheduleViewState extends State { List _buildCountDownAngInchingView( BuildContext context, WaterHeaterDeviceStatusLoaded state) { + final isCountDown = + state.scheduleMode?.name == ScheduleModes.countdown.name; return [ Text( - 'Countdown:', + isCountDown ? 'Countdown:' : 'Inching:', style: context.textTheme.bodySmall!.copyWith( fontSize: 13, color: ColorsManager.grayColor, ), ), - const SizedBox(height: 4), + 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: [ @@ -580,7 +668,7 @@ class _BuildScheduleViewState extends State { hours: value, minutes: state.minutes ?? 0, )); - }), + }, isActive: isActive), const SizedBox(width: 10), _buildPickerColumn(context, 'm', state.minutes ?? 0, 60, (value) { context.read().add(UpdateScheduleEvent( @@ -588,18 +676,19 @@ class _BuildScheduleViewState extends State { hours: state.hours ?? 0, minutes: value, )); - }), + }, isActive: isActive), ], ); } Widget _buildPickerColumn(BuildContext context, String label, - int initialValue, int itemCount, ValueChanged onSelected) { + int initialValue, int itemCount, ValueChanged onSelected, + {required bool isActive}) { return Row( mainAxisSize: MainAxisSize.min, children: [ Container( - height: 50, + height: 40, width: 80, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( @@ -619,7 +708,10 @@ class _BuildScheduleViewState extends State { return Center( child: Text( index.toString().padLeft(2, '0'), - style: const TextStyle(fontSize: 24), + style: TextStyle( + fontSize: 24, + color: isActive ? ColorsManager.grayColor : Colors.black, + ), ), ); }, @@ -627,7 +719,7 @@ class _BuildScheduleViewState extends State { ), ), ), - const SizedBox(height: 8), + const SizedBox(width: 8), Text( label, style: const TextStyle( diff --git a/lib/utils/theme/theme.dart b/lib/utils/theme/theme.dart index ee868c8d..413f3243 100644 --- a/lib/utils/theme/theme.dart +++ b/lib/utils/theme/theme.dart @@ -5,9 +5,10 @@ final myTheme = ThemeData( fontFamily: 'Aftika', textTheme: const TextTheme( bodySmall: TextStyle( - fontSize: 13, - color: ColorsManager.whiteColors, - fontWeight: FontWeight.bold), + fontSize: 13, + color: ColorsManager.whiteColors, + fontWeight: FontWeight.normal, + ), bodyMedium: TextStyle(color: Colors.black87, fontSize: 14), bodyLarge: TextStyle(fontSize: 16, color: Colors.white), headlineSmall: TextStyle(color: Colors.black87, fontSize: 18), From f99744338c02a7b5986ae75b7099c7650450a3f9 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 22 Sep 2024 14:17:52 +0300 Subject: [PATCH 32/65] connecting apis and changes the table design to match figma --- assets/icons/empty_records.svg | 15 + .../water_heater/bloc/water_heater_bloc.dart | 141 ++++- .../water_heater/bloc/water_heater_event.dart | 60 +- .../water_heater/bloc/water_heater_state.dart | 38 +- .../water_heater/models/schedule_entry.dart | 19 + .../water_heater/models/send_schedule.dart | 93 +++ .../water_heater/widgets/schedual_view.dart | 528 +++++++++--------- lib/services/devices_mang_api.dart | 60 +- lib/utils/constants/api_const.dart | 6 + lib/utils/constants/assets.dart | 3 + lib/utils/format_date_time.dart | 15 + 11 files changed, 674 insertions(+), 304 deletions(-) create mode 100644 assets/icons/empty_records.svg create mode 100644 lib/pages/device_managment/water_heater/models/schedule_entry.dart create mode 100644 lib/pages/device_managment/water_heater/models/send_schedule.dart diff --git a/assets/icons/empty_records.svg b/assets/icons/empty_records.svg new file mode 100644 index 00000000..662a3e47 --- /dev/null +++ b/assets/icons/empty_records.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index fcf52732..07f73569 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -5,8 +5,11 @@ 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/send_schedule.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'; +import 'package:syncrow_web/utils/format_date_time.dart'; part 'water_heater_event.dart'; part 'water_heater_state.dart'; @@ -21,11 +24,63 @@ class WaterHeaterBloc extends Bloc { on(_onAddSchedule); on(_onDeleteSchedule); on(_onUpdateSchedule); + on(_initializeAddSchedule); + on(_updateSelectedTime); + on(_updateSelectedDay); + on(_updateFunctionOn); } late WaterHeaterStatusModel deviceStatus; Timer? _countdownTimer; + FutureOr _initializeAddSchedule( + InitializeAddScheduleEvent event, + Emitter emit, + ) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + if (event.isEditing) { + emit(currentState.copyWith( + selectedTime: event.selectedTime, + selectedDays: event.selectedDays ?? List.filled(7, false), + functionOn: event.functionOn ?? false, + isEditing: event.isEditing, + )); + } else { + emit(currentState.copyWith( + selectedTime: null, + selectedDays: List.filled(7, false), + functionOn: false, + isEditing: false, + )); + } + } + + FutureOr _updateSelectedTime( + UpdateSelectedTimeEvent event, + Emitter emit, + ) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + emit(currentState.copyWith(selectedTime: event.selectedTime)); + } + + FutureOr _updateSelectedDay( + UpdateSelectedDayEvent event, + Emitter emit, + ) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + final updatedDays = List.from(currentState.selectedDays); + updatedDays[event.index] = event.value; + emit(currentState.copyWith(selectedDays: updatedDays)); + } + + FutureOr _updateFunctionOn( + UpdateFunctionOnEvent event, + Emitter emit, + ) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + emit(currentState.copyWith(functionOn: event.isOn)); + } + FutureOr _updateScheduleEvent( UpdateScheduleEvent event, Emitter emit, @@ -301,21 +356,50 @@ class WaterHeaterBloc extends Bloc { FutureOr _onAddSchedule( AddScheduleEvent event, Emitter emit, - ) { + ) async { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; - final newSchedule = ScheduleEntry( - selectedDays: event.selectedDays, - time: event.time, - functionOn: event.functionOn, - ); - final updatedSchedules = List.from(currentState.schedules) - ..add(newSchedule); - emit(currentState.copyWith(schedules: updatedSchedules)); + SendSchedule sendSchedule = SendSchedule( + category: event.category, + time: formatTimeOfDayToISO(event.time), + function: Status(code: 'switch_1', value: event.functionOn), + days: _getSelectedDaysString(event.selectedDays), + ); + + bool success = await DevicesManagementApi() + .addScheduleRecord(sendSchedule, currentState.status.uuid); + + if (success) { + final newSchedule = ScheduleEntry( + selectedDays: event.selectedDays, + time: event.time, + functionOn: event.functionOn, + category: event.category, + ); + + final updatedSchedules = + List.from(currentState.schedules)..add(newSchedule); + + emit(currentState.copyWith(schedules: updatedSchedules)); + } else { + emit(const WaterHeaterFailedState( + error: 'Failed to add schedule. Please try again.')); + } } } + List _getSelectedDaysString(List selectedDays) { + final days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + List selectedDaysStr = []; + for (int i = 0; i < selectedDays.length; i++) { + if (selectedDays[i]) { + selectedDaysStr.add(days[i]); + } + } + return selectedDaysStr; + } + FutureOr _onDeleteSchedule( DeleteScheduleEvent event, Emitter emit, @@ -332,17 +416,42 @@ class WaterHeaterBloc extends Bloc { FutureOr _onUpdateSchedule( UpdateScheduleEntryEvent event, Emitter emit, - ) { + ) async { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; - final updatedSchedules = List.from(currentState.schedules); - updatedSchedules[event.index] = ScheduleEntry( - selectedDays: event.selectedDays, - time: event.time, - functionOn: event.functionOn, + + // Get the current schedule ID or UUID (assuming it's stored in the schedules) + String scheduleId = + ""; // Retrieve the actual schedule ID based on your model + + // Call the API to update the schedule + bool success = await DevicesManagementApi().updateScheduleRecord( + enable: event.functionOn, + uuid: event.deviceId, + scheduleId: scheduleId, ); - emit(currentState.copyWith(schedules: updatedSchedules)); + if (success) { + final updatedSchedules = + List.from(currentState.schedules); + + final updatedScheduleIndex = updatedSchedules.indexWhere((schedule) { + return schedule.category == event.category; + }); + + if (updatedScheduleIndex != -1) { + updatedSchedules[updatedScheduleIndex] = ScheduleEntry( + category: event.category, + selectedDays: updatedSchedules[updatedScheduleIndex].selectedDays, + time: updatedSchedules[updatedScheduleIndex].time, + functionOn: event.functionOn, + ); + emit(currentState.copyWith(schedules: updatedSchedules)); + } + } else { + emit(const WaterHeaterFailedState( + error: 'Failed to update schedule. Please try again.')); + } } } diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index 360f4a4f..0c9cd794 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -67,15 +67,17 @@ final class AddScheduleEvent extends WaterHeaterEvent { final List selectedDays; final TimeOfDay time; final bool functionOn; + final String category; const AddScheduleEvent({ required this.selectedDays, required this.time, required this.functionOn, + required this.category, }); @override - List get props => [selectedDays, time, functionOn]; + List get props => [selectedDays, time, functionOn, category]; } final class DeleteScheduleEvent extends WaterHeaterEvent { @@ -88,18 +90,60 @@ final class DeleteScheduleEvent extends WaterHeaterEvent { } final class UpdateScheduleEntryEvent extends WaterHeaterEvent { - final int index; - final List selectedDays; - final TimeOfDay time; final bool functionOn; + final String category; + final String deviceId; const UpdateScheduleEntryEvent({ - required this.index, - required this.selectedDays, - required this.time, required this.functionOn, + required this.category, + required this.deviceId, }); @override - List get props => [index, selectedDays, time, functionOn]; + List get props => [category, functionOn, deviceId]; +} + +class InitializeAddScheduleEvent extends WaterHeaterEvent { + final TimeOfDay? selectedTime; + final List? selectedDays; + final bool? functionOn; + final bool isEditing; + final int? index; + + const InitializeAddScheduleEvent({ + this.selectedTime, + this.selectedDays, + this.functionOn, + this.isEditing = false, + this.index, + }); +} + +class UpdateSelectedTimeEvent extends WaterHeaterEvent { + final TimeOfDay selectedTime; + + const UpdateSelectedTimeEvent(this.selectedTime); + + @override + List get props => [selectedTime]; +} + +class UpdateSelectedDayEvent extends WaterHeaterEvent { + final int index; + final bool value; + + const UpdateSelectedDayEvent(this.index, this.value); + + @override + List get props => [index, value]; +} + +class UpdateFunctionOnEvent extends WaterHeaterEvent { + final bool isOn; + + const UpdateFunctionOnEvent(this.isOn); + + @override + List get props => [isOn]; } diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart index 95c68f24..1054104f 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart @@ -13,7 +13,7 @@ final class WaterHeaterInitial extends WaterHeaterState {} final class WaterHeaterLoadingState extends WaterHeaterState {} -final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { +class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { final WaterHeaterStatusModel status; final ScheduleModes? scheduleMode; final int? hours; @@ -21,6 +21,10 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { final bool? isActive; final Duration? countdownRemaining; final List schedules; + final List selectedDays; + final TimeOfDay? selectedTime; + final bool functionOn; + final bool isEditing; const WaterHeaterDeviceStatusLoaded( this.status, { @@ -30,6 +34,10 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { this.isActive, this.countdownRemaining, this.schedules = const [], + this.selectedDays = const [false, false, false, false, false, false, false], + this.selectedTime, + this.functionOn = false, + this.isEditing = false, }); @override @@ -41,6 +49,10 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { isActive, countdownRemaining, schedules, + selectedDays, + selectedTime, + functionOn, + isEditing ]; WaterHeaterDeviceStatusLoaded copyWith({ @@ -51,6 +63,10 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { bool? isActive, Duration? countdownRemaining, List? schedules, + List? selectedDays, + TimeOfDay? selectedTime, + bool? functionOn, + bool? isEditing, }) { return WaterHeaterDeviceStatusLoaded( status ?? this.status, @@ -60,26 +76,14 @@ final class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { isActive: isActive ?? this.isActive, countdownRemaining: countdownRemaining ?? this.countdownRemaining, schedules: schedules ?? this.schedules, + selectedDays: selectedDays ?? this.selectedDays, + selectedTime: selectedTime ?? this.selectedTime, + functionOn: functionOn ?? this.functionOn, + isEditing: isEditing ?? this.isEditing, ); } } -class ScheduleEntry { - final List selectedDays; - final TimeOfDay time; - final bool functionOn; - - ScheduleEntry({ - required this.selectedDays, - required this.time, - required this.functionOn, - }); - - @override - String toString() => - 'ScheduleEntry(selectedDays: $selectedDays, time: $time, functionOn: $functionOn)'; -} - final class WaterHeaterFailedState extends WaterHeaterState { final String error; diff --git a/lib/pages/device_managment/water_heater/models/schedule_entry.dart b/lib/pages/device_managment/water_heater/models/schedule_entry.dart new file mode 100644 index 00000000..2d02b617 --- /dev/null +++ b/lib/pages/device_managment/water_heater/models/schedule_entry.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class ScheduleEntry { + final List selectedDays; + final TimeOfDay time; + final bool functionOn; + final String category; + + ScheduleEntry({ + required this.selectedDays, + required this.time, + required this.functionOn, + required this.category, + }); + + @override + String toString() => + 'ScheduleEntry(selectedDays: $selectedDays, time: $time, functionOn: $functionOn)'; +} \ No newline at end of file diff --git a/lib/pages/device_managment/water_heater/models/send_schedule.dart b/lib/pages/device_managment/water_heater/models/send_schedule.dart new file mode 100644 index 00000000..650f3a04 --- /dev/null +++ b/lib/pages/device_managment/water_heater/models/send_schedule.dart @@ -0,0 +1,93 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +/* +{ + "category": "kg", + "time": "2024-09-22T10:31:54Z", + "function": { + "code": "switch_1", + "value": true + }, + "days": [ + "Sun" + ] +} +*/ + +class SendSchedule { + final String category; + final String time; + final Status function; + final List days; + + SendSchedule({ + required this.category, + required this.time, + required this.function, + required this.days, + }); + + SendSchedule copyWith({ + String? category, + String? time, + Status? function, + List? days, + }) { + return SendSchedule( + category: category ?? this.category, + time: time ?? this.time, + function: function ?? this.function, + days: days ?? this.days, + ); + } + + Map toMap() { + return { + 'category': category, + 'time': time, + 'function': function.toMap(), + 'days': days, + }; + } + + factory SendSchedule.fromMap(Map map) { + return SendSchedule( + category: map['category'] ?? '', + time: map['time'] ?? '', + function: Status.fromMap(map['function']), + days: List.from(map['days']), + ); + } + + String toJson() => json.encode(toMap()); + + factory SendSchedule.fromJson(String source) => + SendSchedule.fromMap(json.decode(source)); + + @override + String toString() { + return 'SendSchedule(category: $category, time: $time, function: $function, days: $days)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is SendSchedule && + 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; + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart index 125a9483..7ef86bac 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart @@ -1,9 +1,12 @@ 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 { @@ -171,7 +174,8 @@ class _BuildScheduleViewState extends State { padding: 2, backgroundColor: ColorsManager.graysColor, borderRadius: 15, - onPressed: () => _showAddScheduleDialog(context), + onPressed: () => + _showAddScheduleDialog(context, schedule: null, index: null), child: Row( children: [ const Icon(Icons.add, color: ColorsManager.primaryColor), @@ -192,52 +196,92 @@ class _BuildScheduleViewState extends State { } Widget _buildScheduleTable(WaterHeaterDeviceStatusLoaded state) { - return Table( - border: TableBorder.all(color: Colors.grey), + return Column( children: [ - TableRow( + Table( + border: TableBorder.all( + color: ColorsManager.graysColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), topRight: Radius.circular(20)), + ), children: [ - _buildTableHeader('Active'), - _buildTableHeader('Days'), - _buildTableHeader('Time'), - _buildTableHeader('Function'), - _buildTableHeader('Action'), - ], - ), - if (state.schedules.isEmpty) - TableRow( - children: [ - Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'No schedules added yet', - style: context.textTheme.bodySmall!.copyWith( - fontSize: 13, - color: ColorsManager.grayColor, - ), - ), + TableRow( + decoration: const BoxDecoration( + color: ColorsManager.boxColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), ), ), - const SizedBox(), - const SizedBox(), - const SizedBox(), - const SizedBox(), - ], + 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)), ), - for (int i = 0; i < state.schedules.length; i++) - _buildScheduleRow(state.schedules[i], i, context), + 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) + ? 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))), @@ -247,9 +291,10 @@ class _BuildScheduleViewState extends State { runAlignment: WrapAlignment.center, children: [ TextButton( + style: TextButton.styleFrom(padding: EdgeInsets.zero), onPressed: () { - _showAddScheduleDialog(context); - // _showEditScheduleDialog(context, schedule, index); + _showAddScheduleDialog(context, + schedule: schedule, index: index); }, child: Text( 'Edit', @@ -258,6 +303,7 @@ class _BuildScheduleViewState extends State { ), ), TextButton( + style: TextButton.styleFrom(padding: EdgeInsets.zero), onPressed: () { context .read() @@ -287,245 +333,165 @@ class _BuildScheduleViewState extends State { return selectedDaysStr.join(', '); } - void _showEditScheduleDialog( - BuildContext context, ScheduleEntry schedule, int index) { - List selectedDays = List.from(schedule.selectedDays); - TimeOfDay selectedTime = schedule.time; - bool isOn = schedule.functionOn; - - showDialog( - context: context, - builder: (ctx) { - return StatefulBuilder( - builder: (context, setState) { - return AlertDialog( - title: const Text('Edit Schedule'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ElevatedButton( - onPressed: () async { - TimeOfDay? time = await showTimePicker( - context: context, - initialTime: selectedTime, - ); - if (time != null) { - setState(() { - selectedTime = time; - }); - } - }, - child: Text(selectedTime.format(context)), - ), - ], - ), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - context.read().add( - UpdateScheduleEntryEvent( - index: index, - selectedDays: selectedDays, - time: selectedTime, - functionOn: isOn, - ), - ); - Navigator.pop(context); - }, - child: const Text('Save'), - ), - ], - ); - }, - ); - }, - ); - } - Widget _buildTableHeader(String label) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - label, - style: const TextStyle(fontWeight: FontWeight.bold), + 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) { + void _showAddScheduleDialog(BuildContext context, + {ScheduleEntry? schedule, int? index}) { + final bloc = context.read(); + + 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) { - List selectedDays = [ - false, - false, - false, - false, - false, - false, - false - ]; - - TimeOfDay? selectedTime; - bool isOn = false; return BlocProvider.value( - value: BlocProvider.of(context), - child: StatefulBuilder( - builder: (ctx, setState) { - 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, + value: bloc, + child: BlocBuilder( + 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(), - ], - ), - const SizedBox( - height: 24, + ), + 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: 150, - height: 40, + width: 200, child: DefaultButton( - padding: 8, - backgroundColor: ColorsManager.boxColor, - borderRadius: 15, - onPressed: () async { - TimeOfDay? time = await showTimePicker( - context: context, - initialTime: TimeOfDay.now(), - ); - if (time != null) { - setState(() { - selectedTime = time; - }); + 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); } }, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - selectedTime == null - ? 'Time' - : selectedTime!.format(context), - style: context.textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, - ), - ), - const Icon( - Icons.access_time, - color: ColorsManager.grayColor, - size: 18, - ), - ], - ), + backgroundColor: ColorsManager.primaryColor, + child: const Text('Save'), ), ), - const SizedBox(height: 16), - Row( - children: [ - _buildDayCheckbox(setState, 'Mon.', 0, selectedDays), - _buildDayCheckbox(setState, 'Tue.', 1, selectedDays), - _buildDayCheckbox(setState, 'Wed.', 2, selectedDays), - _buildDayCheckbox(setState, 'Thu.', 3, selectedDays), - _buildDayCheckbox(setState, 'Fri.', 4, selectedDays), - _buildDayCheckbox(setState, 'Sat.', 5, selectedDays), - _buildDayCheckbox(setState, 'Sun.', 6, selectedDays), - ], - ), - const SizedBox(height: 16), - Row( - children: [ - Text( - 'Function:', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.grayColor), - ), - const SizedBox( - width: 10, - ), - Radio( - value: true, - groupValue: isOn, - onChanged: (bool? value) { - setState(() { - isOn = value!; - }); - }, - ), - const Text('On'), - const SizedBox( - width: 10, - ), - Radio( - value: false, - groupValue: isOn, - onChanged: (bool? value) { - setState(() { - isOn = value!; - }); - }, - ), - const Text('Off'), - ], - ), ], - ), - 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 (selectedTime != null) { - context.read().add(AddScheduleEvent( - time: selectedTime!, - selectedDays: selectedDays, - functionOn: isOn, - )); - Navigator.pop(context); - } - }, - backgroundColor: ColorsManager.primaryColor, - child: const Text('Save'), - ), - ), - ], - ); + ); + } + return const SizedBox(); }, ), ); @@ -533,19 +499,57 @@ class _BuildScheduleViewState extends State { ); } - Widget _buildDayCheckbox(void Function(void Function()) setState, String day, - int index, List selectedDays) { + Widget _buildDayCheckboxes(BuildContext context, List 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() + .add(UpdateSelectedDayEvent(index, value!)); + }, + ), + Text(dayLabels[index]), + ], + ); + }), + ); + } + + Widget _buildFunctionSwitch(BuildContext context, bool isOn) { return Row( children: [ - Checkbox( - value: selectedDays[index], + Text( + 'Function:', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.grayColor), + ), + const SizedBox(width: 10), + Radio( + value: true, + groupValue: isOn, onChanged: (bool? value) { - setState(() { - selectedDays[index] = value!; - }); + context + .read() + .add(const UpdateFunctionOnEvent(true)); }, ), - Text(day), + const Text('On'), + const SizedBox(width: 10), + Radio( + value: false, + groupValue: isOn, + onChanged: (bool? value) { + context + .read() + .add(const UpdateFunctionOnEvent(false)); + }, + ), + const Text('Off'), ], ); } diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 4be915a2..4445d5c1 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; 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/water_heater/models/send_schedule.dart'; import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -79,7 +80,7 @@ class DevicesManagementApi { body: body, showServerMessage: true, expectedResponseModel: (json) { - return (json['successResults'] as List).isNotEmpty ; + return (json['successResults'] as List).isNotEmpty; }, ); @@ -175,4 +176,61 @@ class DevicesManagementApi { ); } } + + Future addScheduleRecord(SendSchedule sendSchedule, String uuid) async { + try { + final response = await HTTPService().post( + path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), + body: sendSchedule.toMap(), + showServerMessage: true, + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return false; + } + } + + Future updateScheduleRecord( + {required bool enable, + required String uuid, + required String scheduleId}) async { + try { + final response = await HTTPService().put( + path: ApiEndpoints.scheduleByDeviceId + .replaceAll('{deviceUuid}', uuid) + .replaceAll('{scheduleUuid}', scheduleId), + body: { + 'scheduleId': scheduleId, + 'enable': enable, + }, + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return false; + } + } + + Future deleteScheduleRecord(String uuid) async { + try { + final response = await HTTPService().delete( + path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), + showServerMessage: true, + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return false; + } + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 3138c502..0c17e2bc 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -38,4 +38,10 @@ abstract class ApiEndpoints { static const String getDeviceLogs = '/device/report-logs/{uuid}?code={code}'; static const String getDeviceLogsByDate = '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; + + static const String scheduleByDeviceId = '/schedule/{deviceUuid}'; + static const String deleteScheduleByDeviceId = + '/schedule/{deviceUuid}/{scheduleUuid}'; + static const String updateScheduleByDeviceId = + '/schedule/enable/{deviceUuid}'; } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 3c22e031..c334d2d1 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -154,4 +154,7 @@ class Assets { static const String mainDoorReports = 'assets/icons/main_door_reports.svg'; //assets/icons/main_door.svg static const String mainDoor = 'assets/icons/main_door.svg'; + + //assets/icons/empty_records.svg + static const String emptyRecords = 'assets/icons/empty_records.svg'; } diff --git a/lib/utils/format_date_time.dart b/lib/utils/format_date_time.dart index 5c089a2c..e214b46c 100644 --- a/lib/utils/format_date_time.dart +++ b/lib/utils/format_date_time.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; String formatDateTime(DateTime? dateTime) { @@ -9,3 +10,17 @@ String formatDateTime(DateTime? dateTime) { return '${dateFormatter.format(dateTime)} ${timeFormatter.format(dateTime)}'; } + +String formatTimeOfDayToISO(TimeOfDay time, {DateTime? currentDate}) { + final now = currentDate ?? DateTime.now(); + + final dateTime = DateTime( + now.year, + now.month, + now.day, + time.hour, + time.minute, + ); + + return dateTime.toUtc().toIso8601String(); +} \ No newline at end of file From b3d891b2c808a13d33d6076ec22625019e3a1b9c Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 22 Sep 2024 14:51:15 +0300 Subject: [PATCH 33/65] add the delete api --- .../water_heater/bloc/water_heater_bloc.dart | 4 ++-- ...{send_schedule.dart => schedule_model.dart} | 18 +++++++++--------- lib/services/devices_mang_api.dart | 11 +++++++---- 3 files changed, 18 insertions(+), 15 deletions(-) rename lib/pages/device_managment/water_heater/models/{send_schedule.dart => schedule_model.dart} (84%) diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index 07f73569..a96213db 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -6,7 +6,7 @@ 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/send_schedule.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'; import 'package:syncrow_web/utils/format_date_time.dart'; @@ -360,7 +360,7 @@ class WaterHeaterBloc extends Bloc { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; - SendSchedule sendSchedule = SendSchedule( + ScheduleModel sendSchedule = ScheduleModel( category: event.category, time: formatTimeOfDayToISO(event.time), function: Status(code: 'switch_1', value: event.functionOn), diff --git a/lib/pages/device_managment/water_heater/models/send_schedule.dart b/lib/pages/device_managment/water_heater/models/schedule_model.dart similarity index 84% rename from lib/pages/device_managment/water_heater/models/send_schedule.dart rename to lib/pages/device_managment/water_heater/models/schedule_model.dart index 650f3a04..57386136 100644 --- a/lib/pages/device_managment/water_heater/models/send_schedule.dart +++ b/lib/pages/device_managment/water_heater/models/schedule_model.dart @@ -17,26 +17,26 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_sta } */ -class SendSchedule { +class ScheduleModel { final String category; final String time; final Status function; final List days; - SendSchedule({ + ScheduleModel({ required this.category, required this.time, required this.function, required this.days, }); - SendSchedule copyWith({ + ScheduleModel copyWith({ String? category, String? time, Status? function, List? days, }) { - return SendSchedule( + return ScheduleModel( category: category ?? this.category, time: time ?? this.time, function: function ?? this.function, @@ -53,8 +53,8 @@ class SendSchedule { }; } - factory SendSchedule.fromMap(Map map) { - return SendSchedule( + factory ScheduleModel.fromMap(Map map) { + return ScheduleModel( category: map['category'] ?? '', time: map['time'] ?? '', function: Status.fromMap(map['function']), @@ -64,8 +64,8 @@ class SendSchedule { String toJson() => json.encode(toMap()); - factory SendSchedule.fromJson(String source) => - SendSchedule.fromMap(json.decode(source)); + factory ScheduleModel.fromJson(String source) => + ScheduleModel.fromMap(json.decode(source)); @override String toString() { @@ -76,7 +76,7 @@ class SendSchedule { bool operator ==(Object other) { if (identical(this, other)) return true; - return other is SendSchedule && + return other is ScheduleModel && other.category == category && other.time == time && other.function == function && diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 4445d5c1..049538d3 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; 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/water_heater/models/send_schedule.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'; import 'package:syncrow_web/utils/constants/api_const.dart'; @@ -177,7 +177,8 @@ class DevicesManagementApi { } } - Future addScheduleRecord(SendSchedule sendSchedule, String uuid) async { + Future addScheduleRecord( + ScheduleModel sendSchedule, String uuid) async { try { final response = await HTTPService().post( path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), @@ -218,10 +219,12 @@ class DevicesManagementApi { } } - Future deleteScheduleRecord(String uuid) async { + Future deleteScheduleRecord(String uuid, String scheduleId) async { try { final response = await HTTPService().delete( - path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), + path: ApiEndpoints.scheduleByDeviceId + .replaceAll('{deviceUuid}', uuid) + .replaceAll('{scheduleUuid}', scheduleId), showServerMessage: true, expectedResponseModel: (json) { return json['success'] ?? false; From 3a28f0ef9a8d8ae57115d8c9b83181de62b4bc9e Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 22 Sep 2024 20:47:34 +0300 Subject: [PATCH 34/65] refactor schedule view and cleaning --- .../water_heater/bloc/water_heater_bloc.dart | 404 ++++++---- .../water_heater/bloc/water_heater_event.dart | 24 +- .../water_heater/bloc/water_heater_state.dart | 56 +- .../helper/add_schedule_dialog_helper.dart | 246 ++++++ .../water_heater/models/schedule_entry.dart | 32 +- .../water_heater/models/schedule_model.dart | 100 ++- .../models/water_heater_status_model.dart | 12 +- .../view/water_heater_device_control.dart | 10 +- .../widgets/count_down_button.dart | 74 ++ .../widgets/count_down_inching_view.dart | 152 ++++ .../widgets/inching_mode_buttons.dart | 74 ++ .../water_heater/widgets/schedual_view.dart | 725 ++---------------- .../water_heater/widgets/schedule_header.dart | 46 ++ .../widgets/schedule_managment_ui.dart | 50 ++ .../widgets/schedule_mode_buttons.dart | 45 ++ .../widgets/schedule_mode_selector.dart | 86 +++ .../widgets/schedule_row_widget.dart | 76 ++ .../water_heater/widgets/schedule_table.dart | 198 +++++ lib/services/devices_mang_api.dart | 23 + lib/utils/constants/api_const.dart | 2 + lib/utils/format_date_time.dart | 7 +- 21 files changed, 1540 insertions(+), 902 deletions(-) create mode 100644 lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart create mode 100644 lib/pages/device_managment/water_heater/widgets/count_down_button.dart create mode 100644 lib/pages/device_managment/water_heater/widgets/count_down_inching_view.dart create mode 100644 lib/pages/device_managment/water_heater/widgets/inching_mode_buttons.dart create mode 100644 lib/pages/device_managment/water_heater/widgets/schedule_header.dart create mode 100644 lib/pages/device_managment/water_heater/widgets/schedule_managment_ui.dart create mode 100644 lib/pages/device_managment/water_heater/widgets/schedule_mode_buttons.dart create mode 100644 lib/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart create mode 100644 lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart create mode 100644 lib/pages/device_managment/water_heater/widgets/schedule_table.dart diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index a96213db..1eb88912 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -5,7 +5,6 @@ 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'; @@ -21,17 +20,20 @@ class WaterHeaterBloc extends Bloc { on(_updateScheduleEvent); on(_stopScheduleEvent); on(_onDecrementCountdown); - on(_onAddSchedule); - on(_onDeleteSchedule); - on(_onUpdateSchedule); on(_initializeAddSchedule); on(_updateSelectedTime); on(_updateSelectedDay); on(_updateFunctionOn); + + on(_getSchedule); + on(_onAddSchedule); + on(_onDeleteSchedule); + on(_onUpdateSchedule); } late WaterHeaterStatusModel deviceStatus; Timer? _countdownTimer; + // Timer? _inchingTimer; FutureOr _initializeAddSchedule( InitializeAddScheduleEvent event, @@ -87,22 +89,37 @@ class WaterHeaterBloc extends Bloc { ) async { final currentState = state; if (currentState is WaterHeaterDeviceStatusLoaded) { - final countdownRemaining = - // currentState.isActive == true - // ? currentState.countdownRemaining - // : - Duration(hours: event.hours, minutes: event.minutes); + if (event.scheduleMode == ScheduleModes.schedule) { + emit(currentState.copyWith( + scheduleMode: ScheduleModes.schedule, + )); + } + if (event.scheduleMode == ScheduleModes.countdown) { + final countdownRemaining = + Duration(hours: event.hours, minutes: event.minutes); - emit(currentState.copyWith( - scheduleMode: event.scheduleMode, - hours: countdownRemaining.inHours, - minutes: countdownRemaining.inMinutes % 60, - isActive: currentState.isActive, - countdownRemaining: countdownRemaining, - )); + emit(currentState.copyWith( + scheduleMode: ScheduleModes.countdown, + countdownHours: countdownRemaining.inHours, + countdownMinutes: countdownRemaining.inMinutes % 60, + isCountdownActive: currentState.isCountdownActive, + countdownRemaining: countdownRemaining, + )); - if (!currentState.isActive! && countdownRemaining > Duration.zero) { - _startCountdown(emit, countdownRemaining); + if (!currentState.isCountdownActive! && + countdownRemaining > Duration.zero) { + _startCountdownTimer(emit, countdownRemaining); + } + } else if (event.scheduleMode == ScheduleModes.inching) { + final inchingDuration = + Duration(hours: event.hours, minutes: event.minutes); + + emit(currentState.copyWith( + scheduleMode: ScheduleModes.inching, + inchingHours: inchingDuration.inHours, + inchingMinutes: inchingDuration.inMinutes % 60, + isInchingActive: currentState.isInchingActive, + )); } } } @@ -130,28 +147,39 @@ class WaterHeaterBloc extends Bloc { emit: emit, ); - if (success && - (event.code == "countdown_1" || event.code == "switch_inching")) { - final countdownDuration = Duration(seconds: event.value); + if (success) { + if (event.code == "countdown_1") { + final countdownDuration = Duration(seconds: event.value); - emit(currentState.copyWith( - status: deviceStatus, - scheduleMode: deviceStatus.scheduleMode, - hours: countdownDuration.inHours, - minutes: (countdownDuration.inMinutes % 60), - isActive: true, - countdownRemaining: countdownDuration, - )); - if (countdownDuration.inSeconds > 0) { - _startCountdown(emit, countdownDuration); - } else { - _countdownTimer?.cancel(); emit(currentState.copyWith( - hours: 0, - minutes: 0, - isActive: false, - countdownRemaining: Duration.zero, + countdownHours: countdownDuration.inHours, + countdownMinutes: countdownDuration.inMinutes % 60, + countdownRemaining: countdownDuration, + isCountdownActive: true, )); + + if (countdownDuration.inSeconds > 0) { + _startCountdownTimer(emit, countdownDuration); + } else { + _countdownTimer?.cancel(); + emit(currentState.copyWith( + countdownHours: 0, + countdownMinutes: 0, + countdownRemaining: Duration.zero, + isCountdownActive: false, + )); + } + } else if (event.code == "switch_inching") { + final inchingDuration = Duration(seconds: event.value); + //if (inchingDuration.inSeconds > 0) { + // _startInchingTimer(emit, inchingDuration); + // } else { + emit(currentState.copyWith( + inchingHours: inchingDuration.inHours, + inchingMinutes: inchingDuration.inMinutes % 60, + isInchingActive: true, + )); + // } } } } @@ -163,28 +191,30 @@ class WaterHeaterBloc extends Bloc { ) async { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; + final isCountDown = currentState.scheduleMode == ScheduleModes.countdown; _countdownTimer?.cancel(); - deviceStatus = deviceStatus.copyWith( - countdownHours: 0, - countdownMinutes: 0, - scheduleMode: ScheduleModes.countdown, - ); - - emit(currentState.copyWith( - status: deviceStatus, - scheduleMode: ScheduleModes.countdown, - hours: 0, - minutes: 0, - isActive: false, - countdownRemaining: Duration.zero, - )); + if (isCountDown) { + emit(currentState.copyWith( + countdownHours: 0, + countdownMinutes: 0, + countdownRemaining: Duration.zero, + isCountdownActive: false, + )); + } else if (currentState.scheduleMode == ScheduleModes.inching) { + emit(currentState.copyWith( + inchingHours: 0, + inchingMinutes: 0, + isInchingActive: false, + )); + } try { final status = await DevicesManagementApi().deviceControl( event.deviceId, - Status(code: 'countdown_1', value: 0), + Status( + code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0), ); if (!status) { emit(const WaterHeaterFailedState(error: 'Failed to stop schedule.')); @@ -207,30 +237,66 @@ class WaterHeaterBloc extends Bloc { deviceStatus = WaterHeaterStatusModel.fromJson(event.deviceId, status.status); - if (deviceStatus.countdownHours > 0 || - deviceStatus.countdownMinutes > 0) { - final remainingDuration = Duration( + if (deviceStatus.scheduleMode == ScheduleModes.countdown) { + final countdownRemaining = Duration( hours: deviceStatus.countdownHours, minutes: deviceStatus.countdownMinutes, ); - emit(WaterHeaterDeviceStatusLoaded( - deviceStatus, - scheduleMode: deviceStatus.scheduleMode, - hours: deviceStatus.countdownHours, - minutes: deviceStatus.countdownMinutes, - isActive: true, - countdownRemaining: remainingDuration, - )); + if (countdownRemaining > Duration.zero) { + emit(WaterHeaterDeviceStatusLoaded( + deviceStatus, + scheduleMode: ScheduleModes.countdown, + countdownHours: deviceStatus.countdownHours, + countdownMinutes: deviceStatus.countdownMinutes, + isCountdownActive: true, + countdownRemaining: countdownRemaining, + )); + _startCountdownTimer(emit, countdownRemaining); + } else { + emit(WaterHeaterDeviceStatusLoaded( + deviceStatus, + scheduleMode: ScheduleModes.countdown, + countdownHours: 0, + countdownMinutes: 0, + isCountdownActive: false, + countdownRemaining: Duration.zero, + )); + } + } else if (deviceStatus.scheduleMode == ScheduleModes.inching) { + final inchingDuration = Duration( + hours: deviceStatus.inchingHours, + minutes: deviceStatus.inchingMinutes, + ); - _startCountdown(emit, remainingDuration); + if (inchingDuration > Duration.zero) { + emit(WaterHeaterDeviceStatusLoaded( + deviceStatus, + scheduleMode: ScheduleModes.inching, + inchingHours: deviceStatus.inchingHours, + inchingMinutes: deviceStatus.inchingMinutes, + isInchingActive: true, + )); +//_startInchingTimer(emit, inchingDuration); + } else { + emit(WaterHeaterDeviceStatusLoaded( + deviceStatus, + scheduleMode: ScheduleModes.inching, + inchingHours: 0, + inchingMinutes: 0, + isInchingActive: false, + )); + } } else { emit(WaterHeaterDeviceStatusLoaded( deviceStatus, scheduleMode: deviceStatus.scheduleMode, - hours: 0, - minutes: 0, - isActive: false, + countdownHours: 0, + countdownMinutes: 0, + inchingHours: 0, + inchingMinutes: 0, + isCountdownActive: false, + isInchingActive: false, )); } } catch (e) { @@ -238,6 +304,28 @@ class WaterHeaterBloc extends Bloc { } } + void _startCountdownTimer( + Emitter emit, + Duration countdownRemaining, + ) { + _countdownTimer?.cancel(); + + _countdownTimer = Timer.periodic(const Duration(minutes: 1), (timer) { + add(DecrementCountdownEvent()); + }); + } + + // void _startInchingTimer( + // Emitter emit, + // Duration inchingDuration, + // ) { + // _inchingTimer?.cancel(); + + // _inchingTimer = Timer.periodic(const Duration(minutes: 1), (timer) { + // add(DecrementInchingEvent()); + // }); + // } + _onDecrementCountdown( DecrementCountdownEvent event, Emitter emit, @@ -253,9 +341,9 @@ class WaterHeaterBloc extends Bloc { if (newRemaining <= Duration.zero) { _countdownTimer?.cancel(); emit(currentState.copyWith( - hours: 0, - minutes: 0, - isActive: false, + countdownHours: 0, + countdownMinutes: 0, + isCountdownActive: false, countdownRemaining: Duration.zero, )); return; @@ -267,22 +355,44 @@ class WaterHeaterBloc extends Bloc { int newMinutes = (totalSeconds % 3600) ~/ 60; emit(currentState.copyWith( - hours: newHours, - minutes: newMinutes, + countdownHours: newHours, + countdownMinutes: newMinutes, countdownRemaining: newRemaining, )); } } } - void _startCountdown( - Emitter emit, Duration countdownRemaining) { - _countdownTimer?.cancel(); + // FutureOr _onDecrementInching( + // DecrementInchingEvent event, + // Emitter emit, + // ) { + // if (state is WaterHeaterDeviceStatusLoaded) { + // final currentState = state as WaterHeaterDeviceStatusLoaded; - _countdownTimer = Timer.periodic(const Duration(minutes: 1), (timer) { - add(DecrementCountdownEvent()); - }); - } + // if (currentState.inchingHours > 0 || currentState.inchingMinutes > 0) { + // final newRemaining = Duration( + // hours: currentState.inchingHours, + // minutes: currentState.inchingMinutes, + // ) - + // const Duration(minutes: 1); + + // if (newRemaining <= Duration.zero) { + // _inchingTimer?.cancel(); + // emit(currentState.copyWith( + // inchingHours: 0, + // inchingMinutes: 0, + // isInchingActive: false, + // )); + // } else { + // emit(currentState.copyWith( + // inchingHours: newRemaining.inHours, + // inchingMinutes: newRemaining.inMinutes % 60, + // )); + // } + // } + // } + // } Future _runDebounce({ required String deviceId, @@ -353,6 +463,36 @@ class WaterHeaterBloc extends Bloc { } } + @override + Future close() { + _countdownTimer?.cancel(); + return super.close(); + } + + FutureOr _getSchedule( + GetSchedulesEvent event, Emitter emit) async { + emit(ScheduleLoadingState()); + + try { + // List schedules = await DevicesManagementApi() + // .getDeviceSchedules(deviceStatus.uuid, event.category); + + List schedules = const []; + + emit(WaterHeaterDeviceStatusLoaded( + deviceStatus, + schedules: schedules, + scheduleMode: ScheduleModes.schedule, + )); + } catch (e) { + //(const WaterHeaterFailedState(error: 'Failed to fetch schedules.')); + emit(WaterHeaterDeviceStatusLoaded( + deviceStatus, + schedules: const [], + )); + } + } + FutureOr _onAddSchedule( AddScheduleEvent event, Emitter emit, @@ -360,59 +500,30 @@ class WaterHeaterBloc extends Bloc { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; - ScheduleModel sendSchedule = ScheduleModel( + ScheduleModel newSchedule = ScheduleModel( category: event.category, time: formatTimeOfDayToISO(event.time), function: Status(code: 'switch_1', value: event.functionOn), - days: _getSelectedDaysString(event.selectedDays), + days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), ); + // emit(ScheduleLoadingState()); + bool success = await DevicesManagementApi() - .addScheduleRecord(sendSchedule, currentState.status.uuid); + .addScheduleRecord(newSchedule, currentState.status.uuid); if (success) { - final newSchedule = ScheduleEntry( - selectedDays: event.selectedDays, - time: event.time, - functionOn: event.functionOn, - category: event.category, - ); - final updatedSchedules = - List.from(currentState.schedules)..add(newSchedule); + List.from(currentState.schedules)..add(newSchedule); emit(currentState.copyWith(schedules: updatedSchedules)); } else { - emit(const WaterHeaterFailedState( - error: 'Failed to add schedule. Please try again.')); + emit(currentState); + //emit(const WaterHeaterFailedState(error: 'Failed to add schedule.')); } } } - List _getSelectedDaysString(List selectedDays) { - final days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - List selectedDaysStr = []; - for (int i = 0; i < selectedDays.length; i++) { - if (selectedDays[i]) { - selectedDaysStr.add(days[i]); - } - } - return selectedDaysStr; - } - - FutureOr _onDeleteSchedule( - DeleteScheduleEvent event, - Emitter emit, - ) { - if (state is WaterHeaterDeviceStatusLoaded) { - final currentState = state as WaterHeaterDeviceStatusLoaded; - final updatedSchedules = List.from(currentState.schedules) - ..removeAt(event.index); - - emit(currentState.copyWith(schedules: updatedSchedules)); - } - } - FutureOr _onUpdateSchedule( UpdateScheduleEntryEvent event, Emitter emit, @@ -420,44 +531,53 @@ class WaterHeaterBloc extends Bloc { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; - // Get the current schedule ID or UUID (assuming it's stored in the schedules) - String scheduleId = - ""; // Retrieve the actual schedule ID based on your model + ScheduleModel updatedSchedule = currentState.schedules[event.index] + .copyWith( + function: Status(code: 'switch_1', value: event.functionOn)); + + // emit(ScheduleLoadingState()); - // Call the API to update the schedule bool success = await DevicesManagementApi().updateScheduleRecord( enable: event.functionOn, - uuid: event.deviceId, - scheduleId: scheduleId, + uuid: currentState.status.uuid, + scheduleId: event.scheduleId, ); if (success) { final updatedSchedules = - List.from(currentState.schedules); + List.from(currentState.schedules) + ..[event.index] = updatedSchedule; - final updatedScheduleIndex = updatedSchedules.indexWhere((schedule) { - return schedule.category == event.category; - }); - - if (updatedScheduleIndex != -1) { - updatedSchedules[updatedScheduleIndex] = ScheduleEntry( - category: event.category, - selectedDays: updatedSchedules[updatedScheduleIndex].selectedDays, - time: updatedSchedules[updatedScheduleIndex].time, - functionOn: event.functionOn, - ); - emit(currentState.copyWith(schedules: updatedSchedules)); - } + emit(currentState.copyWith(schedules: updatedSchedules)); } else { - emit(const WaterHeaterFailedState( - error: 'Failed to update schedule. Please try again.')); + emit(currentState); + // emit(const WaterHeaterFailedState(error: 'Failed to update schedule.')); } } } - @override - Future close() { - _countdownTimer?.cancel(); - return super.close(); + FutureOr _onDeleteSchedule( + DeleteScheduleEvent event, + Emitter emit, + ) async { + if (state is WaterHeaterDeviceStatusLoaded) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + + // emit(ScheduleLoadingState()); + + bool success = await DevicesManagementApi() + .deleteScheduleRecord(currentState.status.uuid, event.scheduleId); + + if (success) { + final updatedSchedules = + List.from(currentState.schedules) + ..removeAt(event.index); + + emit(currentState.copyWith(schedules: updatedSchedules)); + } else { + emit(currentState); + // emit(const WaterHeaterFailedState(error: 'Failed to delete schedule.')); + } + } } } diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index 0c9cd794..bc0909ab 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -82,26 +82,44 @@ final class AddScheduleEvent extends WaterHeaterEvent { final class DeleteScheduleEvent extends WaterHeaterEvent { final int index; + final String scheduleId; - const DeleteScheduleEvent(this.index); + const DeleteScheduleEvent({required this.index, required this.scheduleId}); @override - List get props => [index]; + List get props => [index, scheduleId]; } final class UpdateScheduleEntryEvent extends WaterHeaterEvent { final bool functionOn; final String category; final String deviceId; + final int index; + final String scheduleId; const UpdateScheduleEntryEvent({ required this.functionOn, required this.category, required this.deviceId, + required this.scheduleId, + required this.index, }); @override - List get props => [category, functionOn, deviceId]; + List get props => [category, functionOn, deviceId, scheduleId, index]; +} + +class GetSchedulesEvent extends WaterHeaterEvent { + final String uuid; + final String category; + + const GetSchedulesEvent({ + required this.uuid, + required this.category, + }); + + @override + List get props => [uuid, category]; } class InitializeAddScheduleEvent extends WaterHeaterEvent { diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart index 1054104f..5ff5ba3d 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart @@ -13,14 +13,24 @@ final class WaterHeaterInitial extends WaterHeaterState {} final class WaterHeaterLoadingState extends WaterHeaterState {} +final class ScheduleLoadingState extends WaterHeaterState {} + class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { final WaterHeaterStatusModel status; final ScheduleModes? scheduleMode; - final int? hours; - final int? minutes; - final bool? isActive; + + // Countdown-specific + final int? countdownHours; + final int? countdownMinutes; final Duration? countdownRemaining; - final List schedules; + final bool? isCountdownActive; + + // Inching-specific + final int? inchingHours; + final int? inchingMinutes; + final bool? isInchingActive; + + final List schedules; final List selectedDays; final TimeOfDay? selectedTime; final bool functionOn; @@ -29,10 +39,13 @@ class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { const WaterHeaterDeviceStatusLoaded( this.status, { this.scheduleMode, - this.hours, - this.minutes, - this.isActive, + this.countdownHours, + this.countdownMinutes, this.countdownRemaining, + this.isCountdownActive, + this.inchingHours, + this.inchingMinutes, + this.isInchingActive, this.schedules = const [], this.selectedDays = const [false, false, false, false, false, false, false], this.selectedTime, @@ -44,10 +57,13 @@ class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { List get props => [ status, scheduleMode, - hours, - minutes, - isActive, + countdownHours, + countdownMinutes, countdownRemaining, + isCountdownActive, + inchingHours, + inchingMinutes, + isInchingActive, schedules, selectedDays, selectedTime, @@ -58,11 +74,14 @@ class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { WaterHeaterDeviceStatusLoaded copyWith({ WaterHeaterStatusModel? status, ScheduleModes? scheduleMode, - int? hours, - int? minutes, - bool? isActive, + int? countdownHours, + int? countdownMinutes, Duration? countdownRemaining, - List? schedules, + bool? isCountdownActive, + int? inchingHours, + int? inchingMinutes, + bool? isInchingActive, + List? schedules, List? selectedDays, TimeOfDay? selectedTime, bool? functionOn, @@ -71,10 +90,13 @@ class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { return WaterHeaterDeviceStatusLoaded( status ?? this.status, scheduleMode: scheduleMode ?? this.scheduleMode, - hours: hours ?? this.hours, - minutes: minutes ?? this.minutes, - isActive: isActive ?? this.isActive, + countdownHours: countdownHours ?? this.countdownHours, + countdownMinutes: countdownMinutes ?? this.countdownMinutes, countdownRemaining: countdownRemaining ?? this.countdownRemaining, + isCountdownActive: isCountdownActive ?? this.isCountdownActive, + inchingHours: inchingHours ?? this.inchingHours, + inchingMinutes: inchingMinutes ?? this.inchingMinutes, + isInchingActive: isInchingActive ?? this.isInchingActive, schedules: schedules ?? this.schedules, selectedDays: selectedDays ?? this.selectedDays, selectedTime: selectedTime ?? this.selectedTime, diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart new file mode 100644 index 00000000..68e364d7 --- /dev/null +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -0,0 +1,246 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; + +class ScheduleDialogHelper { + static void showAddScheduleDialog(BuildContext context, + {ScheduleModel? schedule, int? index, bool? isEdit}) { + final bloc = context.read(); + + if (schedule != null) { + final time = _convertStringToTimeOfDay(schedule.time); + final selectedDays = _convertDaysStringToBooleans(schedule.days); + + bloc.add(InitializeAddScheduleEvent( + selectedTime: time, + selectedDays: selectedDays, + functionOn: schedule.function.value, + 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( + 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: isEdit == true + ? null + : () 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, + isEdit: isEdit), + 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( + index: index, + deviceId: state.status.uuid, + category: 'kg', + functionOn: state.functionOn, + scheduleId: state.schedules[index].scheduleId, + )); + } 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(); + }, + ), + ); + }, + ); + } + + static TimeOfDay _convertStringToTimeOfDay(String timeString) { + final DateTime dateTime = DateTime.parse(timeString); + return TimeOfDay(hour: dateTime.hour, minute: dateTime.minute); + } + + static List _convertDaysStringToBooleans(List selectedDays) { + final daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + List daysBoolean = List.filled(7, false); + + for (int i = 0; i < daysOfWeek.length; i++) { + if (selectedDays.contains(daysOfWeek[i])) { + daysBoolean[i] = true; + } + } + + return daysBoolean; + } + + static Widget _buildDayCheckboxes( + BuildContext context, List selectedDays, + {bool? isEdit}) { + final dayLabels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + + return Row( + children: List.generate(7, (index) { + return Row( + children: [ + Checkbox( + value: selectedDays[index], + onChanged: isEdit == true + ? null + : (bool? value) { + context + .read() + .add(UpdateSelectedDayEvent(index, value!)); + }, + ), + Text(dayLabels[index]), + ], + ); + }), + ); + } + + static Widget _buildFunctionSwitch(BuildContext context, bool isOn) { + return Row( + children: [ + Text( + 'Function:', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.grayColor), + ), + const SizedBox(width: 10), + Radio( + value: true, + groupValue: isOn, + onChanged: (bool? value) { + context + .read() + .add(const UpdateFunctionOnEvent(true)); + }, + ), + const Text('On'), + const SizedBox(width: 10), + Radio( + value: false, + groupValue: isOn, + onChanged: (bool? value) { + context + .read() + .add(const UpdateFunctionOnEvent(false)); + }, + ), + const Text('Off'), + ], + ); + } +} diff --git a/lib/pages/device_managment/water_heater/models/schedule_entry.dart b/lib/pages/device_managment/water_heater/models/schedule_entry.dart index 2d02b617..8b79f563 100644 --- a/lib/pages/device_managment/water_heater/models/schedule_entry.dart +++ b/lib/pages/device_managment/water_heater/models/schedule_entry.dart @@ -1,19 +1,19 @@ -import 'package:flutter/material.dart'; +// import 'package:flutter/material.dart'; -class ScheduleEntry { - final List selectedDays; - final TimeOfDay time; - final bool functionOn; - final String category; +// class ScheduleEntry { +// final List selectedDays; +// final TimeOfDay time; +// final bool functionOn; +// final String category; - ScheduleEntry({ - required this.selectedDays, - required this.time, - required this.functionOn, - required this.category, - }); +// ScheduleEntry({ +// required this.selectedDays, +// required this.time, +// required this.functionOn, +// required this.category, +// }); - @override - String toString() => - 'ScheduleEntry(selectedDays: $selectedDays, time: $time, functionOn: $functionOn)'; -} \ No newline at end of file +// @override +// String toString() => +// 'ScheduleEntry(selectedDays: $selectedDays, time: $time, functionOn: $functionOn)'; +// } \ No newline at end of file diff --git a/lib/pages/device_managment/water_heater/models/schedule_model.dart b/lib/pages/device_managment/water_heater/models/schedule_model.dart index 57386136..7e9410be 100644 --- a/lib/pages/device_managment/water_heater/models/schedule_model.dart +++ b/lib/pages/device_managment/water_heater/models/schedule_model.dart @@ -1,49 +1,27 @@ import 'dart:convert'; - -import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; - -/* -{ - "category": "kg", - "time": "2024-09-22T10:31:54Z", - "function": { - "code": "switch_1", - "value": true - }, - "days": [ - "Sun" - ] -} -*/ +import 'package:flutter/foundation.dart'; class ScheduleModel { + final String scheduleId; final String category; final String time; final Status function; - final List days; + final List days; + final TimeOfDay? timeOfDay; + final List? selectedDays; ScheduleModel({ required this.category, required this.time, required this.function, required this.days, + this.timeOfDay, + this.selectedDays, + this.scheduleId = '', }); - ScheduleModel copyWith({ - String? category, - String? time, - Status? function, - List? days, - }) { - return ScheduleModel( - category: category ?? this.category, - time: time ?? this.time, - function: function ?? this.function, - days: days ?? this.days, - ); - } - Map toMap() { return { 'category': category, @@ -55,10 +33,15 @@ class ScheduleModel { factory ScheduleModel.fromMap(Map map) { return ScheduleModel( + scheduleId: map['scheduleId'] ?? '', category: map['category'] ?? '', time: map['time'] ?? '', function: Status.fromMap(map['function']), days: List.from(map['days']), + timeOfDay: + parseTimeOfDay(map['time']), + selectedDays: + parseSelectedDays(map['days']), ); } @@ -67,9 +50,54 @@ class ScheduleModel { factory ScheduleModel.fromJson(String source) => ScheduleModel.fromMap(json.decode(source)); + ScheduleModel copyWith({ + String? category, + String? time, + Status? function, + List? days, + TimeOfDay? timeOfDay, + List? selectedDays, + String? scheduleId, + }) { + return ScheduleModel( + 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, + ); + } + + static TimeOfDay? parseTimeOfDay(String isoTime) { + try { + final dateTime = DateTime.parse(isoTime); + return TimeOfDay(hour: dateTime.hour, minute: dateTime.minute); + } catch (e) { + return null; + } + } + + static List parseSelectedDays(List days) { + const allDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + return allDays.map((day) => days.contains(day)).toList(); + } + + static List convertSelectedDaysToStrings(List selectedDays) { + const allDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + List result = []; + for (int i = 0; i < selectedDays.length; i++) { + if (selectedDays[i]) { + result.add(allDays[i]); + } + } + return result; + } + @override String toString() { - return 'SendSchedule(category: $category, time: $time, function: $function, days: $days)'; + return 'ScheduleModel(category: $category, time: $time, function: $function, days: $days, timeOfDay: $timeOfDay, selectedDays: $selectedDays)'; } @override @@ -80,7 +108,9 @@ class ScheduleModel { other.category == category && other.time == time && other.function == function && - listEquals(other.days, days); + listEquals(other.days, days) && + timeOfDay == other.timeOfDay && + listEquals(other.selectedDays, selectedDays); } @override @@ -88,6 +118,8 @@ class ScheduleModel { return category.hashCode ^ time.hashCode ^ function.hashCode ^ - days.hashCode; + days.hashCode ^ + timeOfDay.hashCode ^ + selectedDays.hashCode; } } diff --git a/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart b/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart index 2eebefa9..c535bda2 100644 --- a/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart +++ b/lib/pages/device_managment/water_heater/models/water_heater_status_model.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.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_model.dart'; enum ScheduleModes { countdown, schedule, circulate, inching } @@ -8,11 +9,14 @@ class WaterHeaterStatusModel extends Equatable { final bool heaterSwitch; final int countdownHours; final int countdownMinutes; + final int inchingHours; + final int inchingMinutes; final ScheduleModes scheduleMode; final String relayStatus; final String cycleTiming; + final List schedules; - WaterHeaterStatusModel({ + const WaterHeaterStatusModel({ required this.uuid, required this.heaterSwitch, required this.countdownHours, @@ -20,6 +24,9 @@ class WaterHeaterStatusModel extends Equatable { required this.relayStatus, required this.cycleTiming, required this.scheduleMode, + required this.schedules, + this.inchingHours = 0, + this.inchingMinutes = 0, }); factory WaterHeaterStatusModel.fromJson(String id, List jsonList) { @@ -60,6 +67,7 @@ class WaterHeaterStatusModel extends Equatable { relayStatus: relayStatus, cycleTiming: cycleTiming, scheduleMode: scheduleMode, + schedules: const [], ); } @@ -71,6 +79,7 @@ class WaterHeaterStatusModel extends Equatable { String? relayStatus, String? cycleTiming, ScheduleModes? scheduleMode, + List? schedules, }) { return WaterHeaterStatusModel( uuid: uuid ?? this.uuid, @@ -80,6 +89,7 @@ class WaterHeaterStatusModel extends Equatable { relayStatus: relayStatus ?? this.relayStatus, cycleTiming: cycleTiming ?? this.cycleTiming, scheduleMode: scheduleMode ?? this.scheduleMode, + schedules: schedules ?? this.schedules, ); } diff --git a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart index e30303ac..c3f9d360 100644 --- a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart +++ b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart @@ -29,16 +29,12 @@ class WaterHeaterDeviceControl extends StatelessWidget return const Center(child: CircularProgressIndicator()); } else if (state is WaterHeaterDeviceStatusLoaded) { return _buildStatusControls(context, state.status); - } - // else if (state is WaterHeaterScheduleViewState) { - // final status = context.read().deviceStatus; - // return _buildStatusControls(context, status); - // } - else if (state is WaterHeaterFailedState || + } else if (state is WaterHeaterFailedState || state is WaterHeaterBatchFailedState) { return const Center(child: Text('Error fetching status')); } else { - return const Center(child: CircularProgressIndicator()); + return const SizedBox( + height: 200, child: Center(child: SizedBox())); } }, )); diff --git a/lib/pages/device_managment/water_heater/widgets/count_down_button.dart b/lib/pages/device_managment/water_heater/widgets/count_down_button.dart new file mode 100644 index 00000000..e60c7def --- /dev/null +++ b/lib/pages/device_managment/water_heater/widgets/count_down_button.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.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/utils/extension/build_context_x.dart'; + +class CountdownModeButtons extends StatelessWidget { + final bool isActive; + final String deviceId; + final int hours; + final int minutes; + + const CountdownModeButtons({ + super.key, + required this.isActive, + required this.deviceId, + required this.hours, + required this.minutes, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + 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: isActive + ? DefaultButton( + height: 40, + onPressed: () { + context + .read() + .add(StopScheduleEvent(deviceId)); + context.read().add( + ToggleWaterHeaterEvent( + deviceId: deviceId, + code: 'countdown_1', + value: 0, + ), + ); + }, + backgroundColor: Colors.red, + child: const Text('Stop'), + ) + : DefaultButton( + height: 40, + onPressed: () { + context.read().add( + ToggleWaterHeaterEvent( + deviceId: deviceId, + code: 'countdown_1', + value: Duration(hours: hours, minutes: minutes) + .inSeconds, + ), + ); + }, + backgroundColor: ColorsManager.primaryColor, + child: const Text('Save'), + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/count_down_inching_view.dart b/lib/pages/device_managment/water_heater/widgets/count_down_inching_view.dart new file mode 100644 index 00000000..53892c20 --- /dev/null +++ b/lib/pages/device_managment/water_heater/widgets/count_down_inching_view.dart @@ -0,0 +1,152 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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/water_heater_status_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class CountdownInchingView extends StatelessWidget { + final WaterHeaterDeviceStatusLoaded state; + + const CountdownInchingView({ + super.key, + required this.state, + }); + + @override + Widget build(BuildContext context) { + final isCountDown = + state.scheduleMode?.name == ScheduleModes.countdown.name; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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 off after a preset time.'), + ), + const SizedBox(height: 8), + _hourMinutesWheel(context, state), + ], + ); + } + + Row _hourMinutesWheel( + BuildContext context, WaterHeaterDeviceStatusLoaded state) { + final isCountDown = + state.scheduleMode?.name == ScheduleModes.countdown.name; + late bool isActive; + if (isCountDown && + state.countdownRemaining != null && + state.isCountdownActive == true) { + isActive = true; + } else if (!isCountDown && + state.countdownRemaining != null && + state.isInchingActive == true) { + isActive = true; + } else { + isActive = false; + } + + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _buildPickerColumn( + context, + 'h', + isCountDown + ? (state.countdownHours ?? 0) + : (state.inchingHours ?? 0), + 24, (value) { + context.read().add(UpdateScheduleEvent( + scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, + hours: value, + minutes: isCountDown + ? (state.countdownMinutes ?? 0) + : (state.inchingMinutes ?? 0), + )); + }, isActive: isActive), + const SizedBox(width: 10), + _buildPickerColumn( + context, + 'm', + isCountDown + ? (state.countdownMinutes ?? 0) + : (state.inchingMinutes ?? 0), + 60, (value) { + context.read().add(UpdateScheduleEvent( + scheduleMode: state.scheduleMode ?? ScheduleModes.countdown, + hours: isCountDown + ? (state.countdownHours ?? 0) + : (state.inchingHours ?? 0), + minutes: value, + )); + }, isActive: isActive), + ], + ); + } + + Widget _buildPickerColumn( + BuildContext context, + String label, + int initialValue, + int itemCount, + ValueChanged 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, + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/inching_mode_buttons.dart b/lib/pages/device_managment/water_heater/widgets/inching_mode_buttons.dart new file mode 100644 index 00000000..8eec5cca --- /dev/null +++ b/lib/pages/device_managment/water_heater/widgets/inching_mode_buttons.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.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/utils/extension/build_context_x.dart'; + +class InchingModeButtons extends StatelessWidget { + final bool isActive; + final String deviceId; + final int hours; + final int minutes; + + const InchingModeButtons({ + Key? key, + required this.isActive, + required this.deviceId, + required this.hours, + required this.minutes, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + 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: isActive + ? DefaultButton( + height: 40, + onPressed: () { + context + .read() + .add(StopScheduleEvent(deviceId)); + context.read().add( + ToggleWaterHeaterEvent( + deviceId: deviceId, + code: 'switch_inching', + value: 0, + ), + ); + }, + backgroundColor: Colors.red, + child: const Text('Stop'), + ) + : DefaultButton( + height: 40, + onPressed: () { + context.read().add( + ToggleWaterHeaterEvent( + deviceId: deviceId, + code: 'switch_inching', + value: Duration(hours: hours, minutes: minutes) + .inSeconds, + ), + ); + }, + backgroundColor: ColorsManager.primaryColor, + child: const Text('Save'), + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart index 7ef86bac..09745eb3 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart @@ -1,13 +1,15 @@ 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/helper/add_schedule_dialog_helper.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'; +import 'package:syncrow_web/pages/device_managment/water_heater/widgets/count_down_button.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/widgets/count_down_inching_view.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/widgets/inching_mode_buttons.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedule_header.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedule_managment_ui.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedule_mode_buttons.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart'; class BuildScheduleView extends StatefulWidget { const BuildScheduleView({super.key, required this.status}); @@ -15,7 +17,7 @@ class BuildScheduleView extends StatefulWidget { final WaterHeaterStatusModel status; @override - _BuildScheduleViewState createState() => _BuildScheduleViewState(); + State createState() => _BuildScheduleViewState(); } class _BuildScheduleViewState extends State { @@ -42,20 +44,50 @@ class _BuildScheduleViewState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _scheduleHeader(context), + const ScheduleHeader(), const SizedBox(height: 20), - _buildScheduleModeSelector(context, state), + ScheduleModeSelector(state: state), const SizedBox(height: 20), if (state.scheduleMode == ScheduleModes.schedule) - _buildScheduleManagementUI(state), + ScheduleManagementUI( + state: state, + onAddSchedule: () => + ScheduleDialogHelper.showAddScheduleDialog( + context, + schedule: null, + index: null, + isEdit: false + ), + ), if (state.scheduleMode == ScheduleModes.countdown || state.scheduleMode == ScheduleModes.inching) - ..._buildCountDownAngInchingView(context, state), + CountdownInchingView(state: state), const SizedBox(height: 20), - _buildSaveStopCancelButtons(context, state), + if (state.scheduleMode == ScheduleModes.countdown) + CountdownModeButtons( + isActive: state.isCountdownActive ?? false, + deviceId: widget.status.uuid, + hours: state.countdownHours ?? 0, + minutes: state.countdownMinutes ?? 0, + ), + if (state.scheduleMode == ScheduleModes.inching) + InchingModeButtons( + isActive: state.isInchingActive ?? false, + deviceId: widget.status.uuid, + hours: state.inchingHours ?? 0, + minutes: state.inchingMinutes ?? 0, + ), + if (state.scheduleMode != ScheduleModes.countdown && + state.scheduleMode != ScheduleModes.inching) + ScheduleModeButtons( + onSave: () {}, + ), ], ); } + if (state is WaterHeaterLoadingState) { + return const Center(child: CircularProgressIndicator()); + } return const SizedBox(); }, ), @@ -65,673 +97,4 @@ class _BuildScheduleViewState extends State { ), ); } - - 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( - value: mode, - groupValue: state.scheduleMode, - onChanged: (ScheduleModes? value) { - if (value != null) { - context.read().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() - .add(DeleteScheduleEvent(index)); - }, - child: Text( - 'Delete', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.blueColor), - ), - ), - ], - ), - ), - ], - ); - } - - String _getSelectedDays(List selectedDays) { - final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; - List 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(); - - 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( - 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 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() - .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( - value: true, - groupValue: isOn, - onChanged: (bool? value) { - context - .read() - .add(const UpdateFunctionOnEvent(true)); - }, - ), - const Text('On'), - const SizedBox(width: 10), - Radio( - value: false, - groupValue: isOn, - onChanged: (bool? value) { - context - .read() - .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() - .add(StopScheduleEvent(widget.status.uuid)); - context.read().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().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 _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().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().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 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, - ), - ), - ], - ); - } } diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_header.dart b/lib/pages/device_managment/water_heater/widgets/schedule_header.dart new file mode 100644 index 00000000..87afe430 --- /dev/null +++ b/lib/pages/device_managment/water_heater/widgets/schedule_header.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class ScheduleHeader extends StatelessWidget { + const ScheduleHeader({super.key}); + + @override + Widget build(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: const EdgeInsets.all(1), + icon: const Icon( + Icons.close, + color: Colors.grey, + size: 18, + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_managment_ui.dart b/lib/pages/device_managment/water_heater/widgets/schedule_managment_ui.dart new file mode 100644 index 00000000..1710c439 --- /dev/null +++ b/lib/pages/device_managment/water_heater/widgets/schedule_managment_ui.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.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/widgets/schedule_table.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class ScheduleManagementUI extends StatelessWidget { + final WaterHeaterDeviceStatusLoaded state; + final Function onAddSchedule; + + const ScheduleManagementUI({ + super.key, + required this.state, + required this.onAddSchedule, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 170, + height: 40, + child: DefaultButton( + borderColor: ColorsManager.boxColor, + padding: 2, + backgroundColor: ColorsManager.graysColor, + borderRadius: 15, + onPressed: () => onAddSchedule(), + 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), + ScheduleTableWidget(state: state), + ], + ); + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_mode_buttons.dart b/lib/pages/device_managment/water_heater/widgets/schedule_mode_buttons.dart new file mode 100644 index 00000000..f1307d5f --- /dev/null +++ b/lib/pages/device_managment/water_heater/widgets/schedule_mode_buttons.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class ScheduleModeButtons extends StatelessWidget { + final VoidCallback onSave; + + const ScheduleModeButtons({ + super.key, + required this.onSave, + }); + + @override + Widget build(BuildContext context) { + return 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: DefaultButton( + height: 40, + onPressed: onSave, + backgroundColor: ColorsManager.primaryColor, + child: const Text('Save'), + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart b/lib/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart new file mode 100644 index 00000000..7afd96de --- /dev/null +++ b/lib/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; + +class ScheduleModeSelector extends StatelessWidget { + final WaterHeaterDeviceStatusLoaded state; + + const ScheduleModeSelector({super.key, required this.state}); + + @override + Widget build(BuildContext context) { + 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( + value: mode, + groupValue: state.scheduleMode, + onChanged: (ScheduleModes? value) { + if (value != null) { + if (value == ScheduleModes.countdown) { + context.read().add(UpdateScheduleEvent( + scheduleMode: value, + hours: state.countdownHours ?? 0, + minutes: state.countdownMinutes ?? 0, + )); + } else if (value == ScheduleModes.inching) { + context.read().add(UpdateScheduleEvent( + scheduleMode: value, + hours: state.inchingHours ?? 0, + minutes: state.inchingMinutes ?? 0, + )); + } + + if (value == ScheduleModes.schedule) { + context.read().add( + GetSchedulesEvent( + category: 'kg', + uuid: state.status.uuid, + ), + ); + } + } + }, + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart b/lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart new file mode 100644 index 00000000..760b86c7 --- /dev/null +++ b/lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart @@ -0,0 +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'; + +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, + }); + + @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), + ), + ), + ], + ), + ), + ], + ), + ], + ); + } + + String _getSelectedDays(List selectedDays) { + final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + List selectedDaysStr = []; + for (int i = 0; i < selectedDays.length; i++) { + if (selectedDays[i]) { + selectedDaysStr.add(days[i]); + } + } + return selectedDaysStr.join(', '); + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_table.dart b/lib/pages/device_managment/water_heater/widgets/schedule_table.dart new file mode 100644 index 00000000..b997c7e0 --- /dev/null +++ b/lib/pages/device_managment/water_heater/widgets/schedule_table.dart @@ -0,0 +1,198 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.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_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'; +import 'package:syncrow_web/utils/format_date_time.dart'; + +import '../helper/add_schedule_dialog_helper.dart'; + +class ScheduleTableWidget extends StatelessWidget { + final WaterHeaterDeviceStatusLoaded state; + + const ScheduleTableWidget({ + super.key, + required this.state, + }); + + @override + Widget build(BuildContext context) { + 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'), + ], + ), + ], + ), + BlocBuilder( + builder: (context, state) { + if (state is ScheduleLoadingState) { + return const SizedBox( + height: 200, + child: Center(child: CircularProgressIndicator())); + } + if (state is WaterHeaterDeviceStatusLoaded && + state.schedules.isEmpty) { + return _buildEmptyState(context); + } else if (state is WaterHeaterDeviceStatusLoaded) { + return Container( + height: 200, + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.graysColor), + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20)), + ), + child: _buildTableBody(state, context)); + } + return const SizedBox(); + }, + ), + ], + ); + } + + Widget _buildEmptyState(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.graysColor), + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), + ), + child: 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, BuildContext context) { + 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), + ], + ), + ); + } + + Widget _buildTableHeader(String label) { + return TableCell( + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + label, + style: const TextStyle( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + ), + ); + } + + TableRow _buildScheduleRow( + ScheduleModel schedule, int index, BuildContext context) { + return 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( + ScheduleModel.parseSelectedDays(schedule.days)))), + 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: () { + ScheduleDialogHelper.showAddScheduleDialog(context, + schedule: schedule, index: index, isEdit: true); + }, + child: Text( + 'Edit', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blueColor), + ), + ), + TextButton( + style: TextButton.styleFrom(padding: EdgeInsets.zero), + onPressed: () { + context.read().add(DeleteScheduleEvent( + index: index, + scheduleId: schedule.scheduleId, + )); + }, + child: Text( + 'Delete', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blueColor), + ), + ), + ], + ), + ), + ], + ); + } + + String _getSelectedDays(List selectedDays) { + final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + List selectedDaysStr = []; + for (int i = 0; i < selectedDays.length; i++) { + if (selectedDays[i]) { + selectedDaysStr.add(days[i]); + } + } + return selectedDaysStr.join(', '); + } +} diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 049538d3..9ac42eca 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -195,6 +195,29 @@ class DevicesManagementApi { } } + Future> getDeviceSchedules( + String uuid, String category) async { + try { + final response = await HTTPService().get( + path: ApiEndpoints.scheduleByDeviceId + .replaceAll('{deviceUuid}', uuid) + .replaceAll('{category}', category), + showServerMessage: true, + expectedResponseModel: (json) { + List schedules = []; + for (var schedule in json['schedules']) { + schedules.add(ScheduleModel.fromJson(schedule)); + } + return schedules; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return []; + } + } + Future updateScheduleRecord( {required bool enable, required String uuid, diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 0c17e2bc..2b39fc03 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -40,6 +40,8 @@ abstract class ApiEndpoints { '/device/report-logs/{uuid}?code={code}&startTime={startTime}&endTime={endTime}'; static const String scheduleByDeviceId = '/schedule/{deviceUuid}'; + static const String getScheduleByDeviceId = + '/schedule/{deviceUuid}?category={category}'; static const String deleteScheduleByDeviceId = '/schedule/{deviceUuid}/{scheduleUuid}'; static const String updateScheduleByDeviceId = diff --git a/lib/utils/format_date_time.dart b/lib/utils/format_date_time.dart index e214b46c..470acb97 100644 --- a/lib/utils/format_date_time.dart +++ b/lib/utils/format_date_time.dart @@ -23,4 +23,9 @@ String formatTimeOfDayToISO(TimeOfDay time, {DateTime? currentDate}) { ); return dateTime.toUtc().toIso8601String(); -} \ No newline at end of file +} + +String formatIsoStringToTime(String isoString) { + final dateTime = DateTime.parse(isoString); + return DateFormat('hh:mm a').format(dateTime); +} From 2955533209ae2d120cf66ef3d377ece389ed7b2b Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 22 Sep 2024 21:56:17 +0300 Subject: [PATCH 35/65] push factory reset logic and call for all devices --- .../device_managment/ac/bloc/ac_bloc.dart | 19 +++++++ .../device_managment/ac/bloc/ac_event.dart | 14 +++++ .../ac/view/ac_device_batch_control.dart | 11 +++- .../models/factory_reset_model.dart | 55 +++++++++++++++++++ .../ceiling_sensor/bloc/bloc.dart | 23 +++++++- .../ceiling_sensor/bloc/event.dart | 14 +++++ .../view/ceiling_sensor_batch_control.dart | 13 ++++- .../curtain/bloc/curtain_bloc.dart | 19 +++++++ .../curtain/bloc/curtain_event.dart | 12 ++++ .../view/curtain_batch_status_view.dart | 12 +++- .../door_lock/bloc/door_lock_bloc.dart | 19 +++++++ .../door_lock/bloc/door_lock_event.dart | 12 ++++ .../view/door_lock_batch_control_view.dart | 15 ++++- .../gateway/bloc/gate_way_bloc.dart | 20 +++++++ .../gateway/bloc/gate_way_event.dart | 12 ++++ .../gateway/view/gateway_batch_control.dart | 13 ++++- .../bloc/wall_light_switch_bloc.dart | 19 +++++++ .../bloc/wall_light_switch_event.dart | 11 ++++ .../view/wall_light_batch_control.dart | 9 ++- .../shared/batch_control/factory_reset.dart | 53 ++++++++++-------- .../bloc/living_room_bloc.dart | 20 +++++++ .../bloc/living_room_event.dart | 9 +++ .../view/living_room_batch_controls.dart | 10 +++- .../bloc/two_gang_switch_bloc.dart | 19 +++++++ .../bloc/two_gang_switch_event.dart | 11 ++++ .../view/wall_light_batch_control.dart | 10 +++- .../wall_sensor/bloc/bloc.dart | 19 +++++++ .../wall_sensor/bloc/event.dart | 11 ++++ .../view/wall_sensor_batch_control.dart | 12 +++- lib/services/devices_mang_api.dart | 18 ++++++ lib/utils/constants/api_const.dart | 2 + 31 files changed, 481 insertions(+), 35 deletions(-) create mode 100644 lib/pages/device_managment/all_devices/models/factory_reset_model.dart diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart index 54f36889..7c6ee628 100644 --- a/lib/pages/device_managment/ac/bloc/ac_bloc.dart +++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart @@ -18,6 +18,7 @@ class AcBloc extends Bloc { on(_onFetchAcBatchStatus); on(_onAcControl); on(_onAcBatchControl); + on(_onFactoryReset); } FutureOr _onFetchAcStatus( @@ -184,4 +185,22 @@ class AcBloc extends Bloc { emit: emit, ); } + + FutureOr _onFactoryReset( + AcFactoryResetEvent event, Emitter emit) async { + emit(AcsLoadingState()); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryResetModel, + event.deviceId, + ); + if (!response) { + emit(const AcsFailedState(error: 'Failed')); + } else { + add(AcFetchDeviceStatusEvent(event.deviceId)); + } + } catch (e) { + emit(AcsFailedState(error: e.toString())); + } + } } diff --git a/lib/pages/device_managment/ac/bloc/ac_event.dart b/lib/pages/device_managment/ac/bloc/ac_event.dart index acb81d95..8d49df96 100644 --- a/lib/pages/device_managment/ac/bloc/ac_event.dart +++ b/lib/pages/device_managment/ac/bloc/ac_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; sealed class AcsEvent extends Equatable { const AcsEvent(); @@ -54,3 +55,16 @@ class AcBatchControlEvent extends AcsEvent { @override List get props => [devicesIds, code, value]; } + +class AcFactoryResetEvent extends AcsEvent { + final String deviceId; + final FactoryResetModel factoryResetModel; + + const AcFactoryResetEvent({ + required this.deviceId, + required this.factoryResetModel, + }); + + @override + List get props => [deviceId, factoryResetModel]; +} diff --git a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart index edda3c5d..987084fa 100644 --- a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart @@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; @@ -89,7 +90,15 @@ class AcDeviceBatchControlView extends StatelessWidget }, ), FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5), - FactoryResetWidget(deviceId: devicesIds.first), + FactoryResetWidget( + callFactoryReset: () { + context.read().add(AcFactoryResetEvent( + deviceId: state.status.uuid, + factoryResetModel: + FactoryResetModel(devicesUuid: devicesIds), + )); + }, + ), ], ); } else if (state is AcsLoadingState) { diff --git a/lib/pages/device_managment/all_devices/models/factory_reset_model.dart b/lib/pages/device_managment/all_devices/models/factory_reset_model.dart new file mode 100644 index 00000000..aec14d16 --- /dev/null +++ b/lib/pages/device_managment/all_devices/models/factory_reset_model.dart @@ -0,0 +1,55 @@ +import 'package:flutter/foundation.dart'; + +class FactoryResetModel { + final List devicesUuid; + + FactoryResetModel({ + required this.devicesUuid, + }); + + factory FactoryResetModel.fromJson(Map json) { + return FactoryResetModel( + devicesUuid: List.from(json['devicesUuid']), + ); + } + + Map toJson() { + return { + 'devicesUuid': devicesUuid, + }; + } + + FactoryResetModel copyWith({ + List? devicesUuid, + }) { + return FactoryResetModel( + devicesUuid: devicesUuid ?? this.devicesUuid, + ); + } + + Map toMap() { + return { + 'devicesUuid': devicesUuid, + }; + } + + factory FactoryResetModel.fromMap(Map map) { + return FactoryResetModel( + devicesUuid: List.from(map['devicesUuid']), + ); + } + + @override + String toString() => 'FactoryReset(devicesUuid: $devicesUuid)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is FactoryResetModel && + listEquals(other.devicesUuid, devicesUuid); + } + + @override + int get hashCode => devicesUuid.hashCode; +} diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart b/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart index ba8e4114..a2382ba6 100644 --- a/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart +++ b/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart @@ -20,14 +20,15 @@ class CeilingSensorBloc extends Bloc { on(_getDeviceReports); on(_showDescription); on(_backToGridView); + on(_onFactoryReset); } void _fetchCeilingSensorStatus( CeilingInitialEvent event, Emitter emit) async { emit(CeilingLoadingInitialState()); try { - var response = await DevicesManagementApi() - .getDeviceStatus(event.deviceId); + var response = + await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = CeilingSensorModel.fromJson(response.status); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); // _listenToChanges(); @@ -188,4 +189,22 @@ class CeilingSensorBloc extends Bloc { return; } } + + FutureOr _onFactoryReset( + CeilingFactoryResetEvent event, Emitter emit) async { + emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus)); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryResetModel, + event.devicesId, + ); + if (!response) { + emit(const CeilingFailedState(error: 'Failed')); + } else { + emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); + } + } catch (e) { + emit(CeilingFailedState(error: e.toString())); + } + } } diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/event.dart b/lib/pages/device_managment/ceiling_sensor/bloc/event.dart index c1efa47c..582c9836 100644 --- a/lib/pages/device_managment/ceiling_sensor/bloc/event.dart +++ b/lib/pages/device_managment/ceiling_sensor/bloc/event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; abstract class CeilingSensorEvent extends Equatable { const CeilingSensorEvent(); @@ -69,3 +70,16 @@ class ShowCeilingDescriptionEvent extends CeilingSensorEvent { } class BackToCeilingGridViewEvent extends CeilingSensorEvent {} + +class CeilingFactoryResetEvent extends CeilingSensorEvent { + final String devicesId; + final FactoryResetModel factoryResetModel; + + const CeilingFactoryResetEvent({ + required this.devicesId, + required this.factoryResetModel, + }); + + @override + List get props => [devicesId, factoryResetModel]; +} diff --git a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart index f95852a5..a6e60c8f 100644 --- a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart +++ b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/bloc.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/event.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/state.dart'; @@ -112,7 +113,17 @@ class CeilingSensorBatchControlView extends StatelessWidget ), ), FirmwareUpdateWidget(deviceId: devicesIds.first, version: 4), - FactoryResetWidget(deviceId: devicesIds.first), + FactoryResetWidget( + callFactoryReset: () { + context.read().add( + CeilingFactoryResetEvent( + devicesId: devicesIds.first, + factoryResetModel: + FactoryResetModel(devicesUuid: devicesIds), + ), + ); + }, + ), ], ); } diff --git a/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart index eb031552..4599f360 100644 --- a/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart +++ b/lib/pages/device_managment/curtain/bloc/curtain_bloc.dart @@ -15,6 +15,7 @@ class CurtainBloc extends Bloc { on(_onFetchBatchStatus); on(_onCurtainControl); on(_onCurtainBatchControl); + on(_onFactoryReset); } FutureOr _onFetchDeviceStatus( @@ -139,4 +140,22 @@ class CurtainBloc extends Bloc { isBatch: true, ); } + + FutureOr _onFactoryReset( + CurtainFactoryReset event, Emitter emit) async { + emit(CurtainStatusLoading()); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryReset, + event.deviceId, + ); + if (!response) { + emit(const CurtainControlError('Failed')); + } else { + add(CurtainFetchDeviceStatus(event.deviceId)); + } + } catch (e) { + emit(CurtainControlError(e.toString())); + } + } } diff --git a/lib/pages/device_managment/curtain/bloc/curtain_event.dart b/lib/pages/device_managment/curtain/bloc/curtain_event.dart index 8ef85145..7236016c 100644 --- a/lib/pages/device_managment/curtain/bloc/curtain_event.dart +++ b/lib/pages/device_managment/curtain/bloc/curtain_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; sealed class CurtainEvent extends Equatable { const CurtainEvent(); @@ -48,3 +49,14 @@ class CurtainBatchControl extends CurtainEvent { @override List get props => [devicesIds, code, value]; } + +class CurtainFactoryReset extends CurtainEvent { + final String deviceId; + final FactoryResetModel factoryReset; + + const CurtainFactoryReset( + {required this.deviceId, required this.factoryReset}); + + @override + List get props => [deviceId, factoryReset]; +} diff --git a/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart b/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart index ec1a0076..b558c837 100644 --- a/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart +++ b/lib/pages/device_managment/curtain/view/curtain_batch_status_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/curtain_toggle.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart'; @@ -68,7 +69,16 @@ class CurtainBatchStatusView extends StatelessWidget }, ), FirmwareUpdateWidget(deviceId: devicesIds.first, version: 5), - FactoryResetWidget(deviceId: devicesIds.first), + FactoryResetWidget( + callFactoryReset: () { + context.read().add( + CurtainFactoryReset( + deviceId: devicesIds.first, + factoryReset: FactoryResetModel(devicesUuid: devicesIds), + ), + ); + }, + ), ], ); } diff --git a/lib/pages/device_managment/door_lock/bloc/door_lock_bloc.dart b/lib/pages/device_managment/door_lock/bloc/door_lock_bloc.dart index 8ad2a05c..c50203f3 100644 --- a/lib/pages/device_managment/door_lock/bloc/door_lock_bloc.dart +++ b/lib/pages/device_managment/door_lock/bloc/door_lock_bloc.dart @@ -17,6 +17,7 @@ class DoorLockBloc extends Bloc { on(_onFetchDeviceStatus); //on(_onDoorLockControl); on(_updateLock); + on(_onFactoryReset); } FutureOr _onFetchDeviceStatus( @@ -113,4 +114,22 @@ class DoorLockBloc extends Bloc { return null; } } + + FutureOr _onFactoryReset( + DoorLockFactoryReset event, Emitter emit) async { + emit(DoorLockStatusLoading()); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryReset, + event.deviceId, + ); + if (!response) { + emit(const DoorLockControlError('Failed')); + } else { + add(DoorLockFetchStatus(event.deviceId)); + } + } catch (e) { + emit(DoorLockControlError(e.toString())); + } + } } diff --git a/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart b/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart index 4033676f..54fa1ddf 100644 --- a/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart +++ b/lib/pages/device_managment/door_lock/bloc/door_lock_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; sealed class DoorLockEvent extends Equatable { const DoorLockEvent(); @@ -38,4 +39,15 @@ class UpdateLockEvent extends DoorLockEvent { List get props => [value]; } +class DoorLockFactoryReset extends DoorLockEvent { + final String deviceId; + final FactoryResetModel factoryReset; + const DoorLockFactoryReset({ + required this.deviceId, + required this.factoryReset, + }); + + @override + List get props => [deviceId, factoryReset]; +} diff --git a/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart b/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart index e4875f12..4efd76fc 100644 --- a/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart +++ b/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart @@ -1,4 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/door_lock/bloc/door_lock_event.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; @@ -30,7 +34,16 @@ class DoorLockBatchControlView extends StatelessWidget deviceId: devicesIds.first, version: 12, ), - FactoryResetWidget(deviceId: devicesIds.first), + FactoryResetWidget( + callFactoryReset: () { + BlocProvider.of(context).add( + DoorLockFactoryReset( + deviceId: devicesIds.first, + factoryReset: FactoryResetModel(devicesUuid: devicesIds), + ), + ); + }, + ), ], ), ); diff --git a/lib/pages/device_managment/gateway/bloc/gate_way_bloc.dart b/lib/pages/device_managment/gateway/bloc/gate_way_bloc.dart index ca572bf0..f4c16ba0 100644 --- a/lib/pages/device_managment/gateway/bloc/gate_way_bloc.dart +++ b/lib/pages/device_managment/gateway/bloc/gate_way_bloc.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; @@ -12,6 +13,7 @@ class GateWayBloc extends Bloc { GateWayBloc() : super(GateWayInitial()) { on((event, emit) {}); on(_getGatWayById); + on(_onFactoryReset); } FutureOr _getGatWayById( @@ -27,4 +29,22 @@ class GateWayBloc extends Bloc { return; } } + + FutureOr _onFactoryReset( + GateWayFactoryReset event, Emitter emit) async { + emit(GatewayLoadingState()); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryReset, + event.deviceId, + ); + if (!response) { + emit(const ErrorState(message: 'Failed')); + } else { + add(GatWayById(event.deviceId)); + } + } catch (e) { + emit(ErrorState(message: e.toString())); + } + } } diff --git a/lib/pages/device_managment/gateway/bloc/gate_way_event.dart b/lib/pages/device_managment/gateway/bloc/gate_way_event.dart index 22c81a12..6ee5faf5 100644 --- a/lib/pages/device_managment/gateway/bloc/gate_way_event.dart +++ b/lib/pages/device_managment/gateway/bloc/gate_way_event.dart @@ -18,3 +18,15 @@ class GatWayById extends GateWayEvent { final String getWayId; const GatWayById(this.getWayId); } + +class GateWayFactoryReset extends GateWayEvent { + final String deviceId; + final FactoryResetModel factoryReset; + const GateWayFactoryReset({ + required this.deviceId, + required this.factoryReset, + }); + + @override + List get props => [deviceId, factoryReset]; +} diff --git a/lib/pages/device_managment/gateway/view/gateway_batch_control.dart b/lib/pages/device_managment/gateway/view/gateway_batch_control.dart index f303cdba..8679a78f 100644 --- a/lib/pages/device_managment/gateway/view/gateway_batch_control.dart +++ b/lib/pages/device_managment/gateway/view/gateway_batch_control.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/gateway/bloc/gate_way_bloc.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; @@ -36,7 +37,17 @@ class GatewayBatchControlView extends StatelessWidget ), children: [ FirmwareUpdateWidget(deviceId: gatewayIds.first, version: 2), - FactoryResetWidget(deviceId: gatewayIds.first), + FactoryResetWidget( + callFactoryReset: () { + context.read().add( + GateWayFactoryReset( + deviceId: gatewayIds.first, + factoryReset: + FactoryResetModel(devicesUuid: gatewayIds), + ), + ); + }, + ), ], ); } else { diff --git a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart index ef25e7ac..595e7e06 100644 --- a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart +++ b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart @@ -15,6 +15,7 @@ class WallLightSwitchBloc on(_onControl); on(_onFetchBatchStatus); on(_onBatchControl); + on(_onFactoryReset); } late WallLightStatusModel deviceStatus; @@ -153,4 +154,22 @@ class WallLightSwitchBloc isBatch: true, ); } + + FutureOr _onFactoryReset( + WallLightFactoryReset event, Emitter emit) async { + emit(WallLightSwitchLoading()); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryReset, + event.deviceId, + ); + if (!response) { + emit(WallLightSwitchError('Failed')); + } else { + emit(WallLightSwitchStatusLoaded(deviceStatus)); + } + } catch (e) { + emit(WallLightSwitchError(e.toString())); + } + } } diff --git a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart index 88c86c97..5c601484 100644 --- a/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart +++ b/lib/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; class WallLightSwitchEvent extends Equatable { @override @@ -46,3 +47,13 @@ class WallLightSwitchBatchControl extends WallLightSwitchEvent { @override List get props => [devicesIds, code, value]; } + +class WallLightFactoryReset extends WallLightSwitchEvent { + final String deviceId; + final FactoryResetModel factoryReset; + + WallLightFactoryReset({required this.deviceId, required this.factoryReset}); + + @override + List get props => [deviceId, factoryReset]; +} diff --git a/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart b/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart index 0c58c6f5..e1dabb61 100644 --- a/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart +++ b/lib/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart'; @@ -77,7 +78,13 @@ class WallLightBatchControlView extends StatelessWidget deviceId: deviceIds.first, version: 12, ), - FactoryResetWidget(deviceId: deviceIds.first), + FactoryResetWidget( + callFactoryReset: () { + context.read().add(WallLightFactoryReset( + deviceId: status.uuid, + factoryReset: FactoryResetModel(devicesUuid: deviceIds))); + }, + ), ], ), ); diff --git a/lib/pages/device_managment/shared/batch_control/factory_reset.dart b/lib/pages/device_managment/shared/batch_control/factory_reset.dart index 98f8f043..78dfc307 100644 --- a/lib/pages/device_managment/shared/batch_control/factory_reset.dart +++ b/lib/pages/device_managment/shared/batch_control/factory_reset.dart @@ -6,36 +6,41 @@ import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class FactoryResetWidget extends StatelessWidget { - const FactoryResetWidget({super.key, required String deviceId}); + const FactoryResetWidget({super.key, required this.callFactoryReset}); + + final Null Function() callFactoryReset; @override Widget build(BuildContext context) { return DeviceControlsContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ClipOval( - child: Container( - color: ColorsManager.whiteColors, - height: 60, - width: 60, - child: Padding( - padding: const EdgeInsets.all(12.0), - child: SvgPicture.asset( - Assets.factoryReset, - fit: BoxFit.cover, + child: GestureDetector( + onTap: callFactoryReset, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ClipOval( + child: Container( + color: ColorsManager.whiteColors, + height: 60, + width: 60, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: SvgPicture.asset( + Assets.factoryReset, + fit: BoxFit.cover, + ), + ), + )), + Text( + 'Factory Reset', + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, ), ), - )), - Text( - 'Factory Reset', - style: context.textTheme.titleMedium!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor, - ), - ), - ], + ], + ), ), ); } diff --git a/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart b/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart index 4ab6f8e8..ca264c13 100644 --- a/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart +++ b/lib/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; @@ -20,6 +21,7 @@ class LivingRoomBloc extends Bloc { on(_livingRoomControl); on(_livingRoomBatchControl); on(_livingRoomFetchBatchControl); + on(_livingRoomFactoryReset); } FutureOr _onFetchDeviceStatus(LivingRoomFetchDeviceStatusEvent event, @@ -165,4 +167,22 @@ class LivingRoomBloc extends Bloc { isBatch: true, ); } + + FutureOr _livingRoomFactoryReset( + LivingRoomFactoryResetEvent event, Emitter emit) async { + emit(LivingRoomDeviceStatusLoading()); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryReset, + event.uuid, + ); + if (!response) { + emit(const LivingRoomDeviceManagementError('Failed')); + } else { + emit(LivingRoomDeviceStatusLoaded(deviceStatus)); + } + } catch (e) { + emit(LivingRoomDeviceManagementError(e.toString())); + } + } } diff --git a/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart b/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart index a3b0d78b..c0ada0f6 100644 --- a/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart +++ b/lib/pages/device_managment/three_gang_switch/bloc/living_room_event.dart @@ -49,3 +49,12 @@ class LivingRoomBatchControl extends LivingRoomEvent { @override List get props => [devicesIds, code, value]; } + +class LivingRoomFactoryResetEvent extends LivingRoomEvent { + final String uuid; + final FactoryResetModel factoryReset; + const LivingRoomFactoryResetEvent(this.uuid, this.factoryReset); + + @override + List get props => [uuid, factoryReset]; +} diff --git a/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart index f8c40179..0d82c515 100644 --- a/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart +++ b/lib/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart'; @@ -105,7 +106,14 @@ class LivingRoomBatchControlsView extends StatelessWidget deviceId: deviceIds.first, version: 12, ), - FactoryResetWidget(deviceId: deviceIds.first), + FactoryResetWidget(callFactoryReset: () { + context.read().add( + LivingRoomFactoryResetEvent( + status.uuid, + FactoryResetModel(devicesUuid: deviceIds), + ), + ); + }), ], ), ); diff --git a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart index 7a15a68c..0d35d8e8 100644 --- a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart +++ b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart @@ -13,6 +13,7 @@ class TwoGangSwitchBloc extends Bloc { on(_onControl); on(_onFetchBatchStatus); on(_onBatchControl); + on(_onFactoryReset); } late TwoGangStatusModel deviceStatus; @@ -155,4 +156,22 @@ class TwoGangSwitchBloc extends Bloc { isBatch: true, ); } + + FutureOr _onFactoryReset( + TwoGangFactoryReset event, Emitter emit) async { + emit(TwoGangSwitchLoading()); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryReset, + event.deviceId, + ); + if (!response) { + emit(TwoGangSwitchError('Failed')); + } else { + emit(TwoGangSwitchStatusLoaded(deviceStatus)); + } + } catch (e) { + emit(TwoGangSwitchError(e.toString())); + } + } } diff --git a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart index d5b9a01d..16973b3a 100644 --- a/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart +++ b/lib/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; class TwoGangSwitchEvent extends Equatable { @override @@ -46,3 +47,13 @@ class TwoGangSwitchBatchControl extends TwoGangSwitchEvent { @override List get props => [deviceId, code, value]; } + +class TwoGangFactoryReset extends TwoGangSwitchEvent { + final String deviceId; + final FactoryResetModel factoryReset; + + TwoGangFactoryReset({required this.deviceId, required this.factoryReset}); + + @override + List get props => [deviceId, factoryReset]; +} diff --git a/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart b/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart index 1e417dfa..52900155 100644 --- a/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart +++ b/lib/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; @@ -87,7 +88,14 @@ class TwoGangBatchControlView extends StatelessWidget deviceId: deviceIds.first, version: 12, ), - FactoryResetWidget(deviceId: deviceIds.first), + FactoryResetWidget(callFactoryReset: () { + context.read().add( + TwoGangFactoryReset( + deviceId: status.uuid, + factoryReset: FactoryResetModel(devicesUuid: deviceIds), + ), + ); + }), ], ), ); diff --git a/lib/pages/device_managment/wall_sensor/bloc/bloc.dart b/lib/pages/device_managment/wall_sensor/bloc/bloc.dart index cc96955a..f3040b48 100644 --- a/lib/pages/device_managment/wall_sensor/bloc/bloc.dart +++ b/lib/pages/device_managment/wall_sensor/bloc/bloc.dart @@ -19,6 +19,7 @@ class WallSensorBloc extends Bloc { on(_getDeviceReports); on(_showDescription); on(_backToGridView); + on(_onFactoryReset); } void _fetchWallSensorStatus( @@ -168,4 +169,22 @@ class WallSensorBloc extends Bloc { BackToGridViewEvent event, Emitter emit) { emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); } + + FutureOr _onFactoryReset( + WallSensorFactoryResetEvent event, Emitter emit) async { + emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus)); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryReset, + event.deviceId, + ); + if (!response) { + emit(const WallSensorFailedState(error: 'Failed')); + } else { + emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); + } + } catch (e) { + emit(WallSensorFailedState(error: e.toString())); + } + } } diff --git a/lib/pages/device_managment/wall_sensor/bloc/event.dart b/lib/pages/device_managment/wall_sensor/bloc/event.dart index f09c7123..17d85d43 100644 --- a/lib/pages/device_managment/wall_sensor/bloc/event.dart +++ b/lib/pages/device_managment/wall_sensor/bloc/event.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; abstract class WallSensorEvent extends Equatable { const WallSensorEvent(); @@ -59,3 +60,13 @@ class WallSensorBatchControlEvent extends WallSensorEvent { @override List get props => [deviceIds, code, value]; } + +class WallSensorFactoryResetEvent extends WallSensorEvent { + final String deviceId; + final FactoryResetModel factoryReset; + + const WallSensorFactoryResetEvent({ + required this.deviceId, + required this.factoryReset, + }); +} diff --git a/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart index dab1e152..5e855208 100644 --- a/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart +++ b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; @@ -118,7 +119,16 @@ class WallSensorBatchControlView extends StatelessWidget ), ), FirmwareUpdateWidget(deviceId: devicesIds.first, version: 2), - FactoryResetWidget(deviceId: devicesIds.first), + FactoryResetWidget( + callFactoryReset: () { + context.read().add( + WallSensorFactoryResetEvent( + deviceId: devicesIds.first, + factoryReset: FactoryResetModel(devicesUuid: devicesIds), + ), + ); + }, + ), ], ); } diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 9ac42eca..5551bfe6 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; 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_model.dart'; import 'package:syncrow_web/pages/visitor_password/model/device_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; @@ -259,4 +260,21 @@ class DevicesManagementApi { return false; } } + + Future factoryReset(FactoryResetModel factoryReset, String uuid) async { + try { + final response = await HTTPService().post( + path: ApiEndpoints.factoryReset.replaceAll('{deviceUuid}', uuid), + body: factoryReset.toMap(), + showServerMessage: true, + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return false; + } + } } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 2b39fc03..23bc5c6c 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -46,4 +46,6 @@ abstract class ApiEndpoints { '/schedule/{deviceUuid}/{scheduleUuid}'; static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}'; + + static const String factoryReset = '/device/factory/reset/{deviceUuid}'; } From b5eeeedcd2e384a8d04b5c72f40a65fb0066842b Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 23 Sep 2024 07:29:04 +0300 Subject: [PATCH 36/65] push ac temp step amount and main door icons --- assets/icons/open_close_door.svg | 24 +++++++++++ assets/icons/open_close_records.svg | 19 +++++++++ .../batch_current_temp.dart | 4 +- .../ac/view/control_list/current_temp.dart | 4 +- .../view/main_door_control_view.dart | 42 ++++++++++++------- lib/utils/constants/assets.dart | 6 +++ 6 files changed, 81 insertions(+), 18 deletions(-) create mode 100644 assets/icons/open_close_door.svg create mode 100644 assets/icons/open_close_records.svg diff --git a/assets/icons/open_close_door.svg b/assets/icons/open_close_door.svg new file mode 100644 index 00000000..d5aacdef --- /dev/null +++ b/assets/icons/open_close_door.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/open_close_records.svg b/assets/icons/open_close_records.svg new file mode 100644 index 00000000..9c5c585c --- /dev/null +++ b/assets/icons/open_close_records.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart b/lib/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart index e6d9378f..ba5ea639 100644 --- a/lib/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart +++ b/lib/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart @@ -118,7 +118,7 @@ class _CurrentTempState extends State { onIncrement: () { if (_adjustedValue < 30) { setState(() { - _adjustedValue++; + _adjustedValue = _adjustedValue + 0.5; }); _onValueChanged(_adjustedValue); } @@ -126,7 +126,7 @@ class _CurrentTempState extends State { onDecrement: () { if (_adjustedValue > 20) { setState(() { - _adjustedValue--; + _adjustedValue = _adjustedValue - 0.5; }); _onValueChanged(_adjustedValue); } diff --git a/lib/pages/device_managment/ac/view/control_list/current_temp.dart b/lib/pages/device_managment/ac/view/control_list/current_temp.dart index 5ff6fea6..0327e357 100644 --- a/lib/pages/device_managment/ac/view/control_list/current_temp.dart +++ b/lib/pages/device_managment/ac/view/control_list/current_temp.dart @@ -118,7 +118,7 @@ class _CurrentTempState extends State { onIncrement: () { if (_adjustedValue < 30) { setState(() { - _adjustedValue++; + _adjustedValue = _adjustedValue + 0.5; }); _onValueChanged(_adjustedValue); } @@ -126,7 +126,7 @@ class _CurrentTempState extends State { onDecrement: () { if (_adjustedValue > 20) { setState(() { - _adjustedValue--; + _adjustedValue = _adjustedValue - 0.5; }); _onValueChanged(_adjustedValue); } diff --git a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart index 2168be5a..44b2fec3 100644 --- a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart +++ b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart @@ -75,16 +75,18 @@ class MainDoorSensorControlView extends StatelessWidget ), children: [ IconNameStatusContainer( + isFullIcon: true, name: status.doorContactState ? 'Open' : 'Close', - icon: Assets.mainDoor, + icon: Assets.openCloseDoor, onTap: () {}, status: status.doorContactState, textColor: ColorsManager.red, paddingAmount: 8, ), IconNameStatusContainer( + isFullIcon: true, name: 'Open/Close\nRecord', - icon: Assets.mainDoorReports, + icon: Assets.openCloseRecords, onTap: () { final from = DateTime.now() .subtract(const Duration(days: 30)) @@ -103,6 +105,7 @@ class MainDoorSensorControlView extends StatelessWidget textColor: ColorsManager.blackColor, ), IconNameStatusContainer( + isFullIcon: false, name: 'Notifications\nSettings', icon: Assets.mainDoorNotifi, onTap: () { @@ -113,6 +116,7 @@ class MainDoorSensorControlView extends StatelessWidget }, status: false, textColor: ColorsManager.blackColor, + paddingAmount: 14, ), ], ); @@ -128,6 +132,7 @@ class IconNameStatusContainer extends StatelessWidget { required this.status, required this.textColor, this.paddingAmount = 12, + required this.isFullIcon, }); final String name; @@ -136,6 +141,7 @@ class IconNameStatusContainer extends StatelessWidget { final bool status; final Color textColor; final double? paddingAmount; + final bool isFullIcon; @override Widget build(BuildContext context) { @@ -145,22 +151,30 @@ class IconNameStatusContainer extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - width: 60, - height: 60, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: ColorsManager.whiteColors, - ), - margin: const EdgeInsets.symmetric(horizontal: 4), - padding: EdgeInsets.all(paddingAmount ?? 12), - child: ClipOval( + if (isFullIcon) + ClipOval( child: SvgPicture.asset( icon, - fit: BoxFit.fill, + fit: BoxFit.contain, + ), + ) + else + Container( + width: 60, + height: 60, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, + ), + //margin: const EdgeInsets.symmetric(horizontal: 4), + padding: EdgeInsets.all(paddingAmount ?? 12), + child: ClipOval( + child: SvgPicture.asset( + icon, + fit: BoxFit.contain, + ), ), ), - ), const Spacer(), Padding( padding: const EdgeInsets.symmetric(horizontal: 6), diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index c334d2d1..719cc21c 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -157,4 +157,10 @@ class Assets { //assets/icons/empty_records.svg static const String emptyRecords = 'assets/icons/empty_records.svg'; + + //assets/icons/open_close_door.svg + static const String openCloseDoor = 'assets/icons/open_close_door.svg'; + + //assets/icons/open_close_records.svg + static const String openCloseRecords = 'assets/icons/open_close_records.svg'; } From 0c530e9ea604fe78de21fdd0b9f1ae6fbf916355 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 23 Sep 2024 08:32:49 +0300 Subject: [PATCH 37/65] push water heater batch control view --- assets/icons/water_heater.svg | 22 +++++ .../helper/route_controls_based_code.dart | 11 ++- .../water_heater/bloc/water_heater_bloc.dart | 73 ++++++++++++++-- .../water_heater/bloc/water_heater_event.dart | 35 +++++--- .../view/water_heater_batch_control.dart | 87 +++++++++++++++++++ .../view/water_heater_device_control.dart | 4 +- lib/utils/constants/assets.dart | 3 + 7 files changed, 217 insertions(+), 18 deletions(-) create mode 100644 assets/icons/water_heater.svg create mode 100644 lib/pages/device_managment/water_heater/view/water_heater_batch_control.dart diff --git a/assets/icons/water_heater.svg b/assets/icons/water_heater.svg new file mode 100644 index 00000000..aa58e3fc --- /dev/null +++ b/assets/icons/water_heater.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 5065ec31..80a00094 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -20,6 +20,7 @@ import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_lig import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_device_control.dart'; mixin RouteControlsBasedCode { @@ -56,7 +57,7 @@ mixin RouteControlsBasedCode { case 'AC': return AcDeviceControlsView(device: device); case 'WH': - return WaterHeaterDeviceControl( + return WaterHeaterDeviceControlView( device: device, ); case 'DS': @@ -140,6 +141,14 @@ mixin RouteControlsBasedCode { .where((e) => (e.productType == 'AC')) .map((e) => e.uuid!) .toList()); + case 'WH': + return WaterHEaterBatchControlView( + deviceIds: devices + .where((e) => (e.productType == 'WH')) + .map((e) => e.uuid!) + .toList(), + ); + default: return const SizedBox(); } diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index 1eb88912..2e84c011 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -17,6 +17,8 @@ class WaterHeaterBloc extends Bloc { WaterHeaterBloc() : super(WaterHeaterInitial()) { on(_fetchWaterHeaterStatus); on(_controlWaterHeater); + on(_batchFetchWaterHeater); + on(_batchControlWaterHeater); on(_updateScheduleEvent); on(_stopScheduleEvent); on(_onDecrementCountdown); @@ -145,6 +147,7 @@ class WaterHeaterBloc extends Bloc { value: event.value, oldValue: oldValue, emit: emit, + isBatch: false, ); if (success) { @@ -395,19 +398,29 @@ class WaterHeaterBloc extends Bloc { // } Future _runDebounce({ - required String deviceId, + required dynamic deviceId, required String code, required dynamic value, required dynamic oldValue, required Emitter emit, + required bool isBatch, }) async { try { + late bool status; await Future.delayed(const Duration(milliseconds: 500)); - final status = await DevicesManagementApi().deviceControl( - deviceId, - Status(code: code, value: value), - ); + if (isBatch) { + status = await DevicesManagementApi().deviceBatchControl( + deviceId, + code, + value, + ); + } else { + status = await DevicesManagementApi().deviceControl( + deviceId, + Status(code: code, value: value), + ); + } if (!status) { _revertValue(code, oldValue, emit.call); @@ -580,4 +593,54 @@ class WaterHeaterBloc extends Bloc { } } } + + FutureOr _batchFetchWaterHeater(FetchWaterHeaterBatchStatusEvent event, + Emitter emit) async { + emit(WaterHeaterLoadingState()); + + try { + final status = + await DevicesManagementApi().getBatchStatus(event.devicesUuid); + deviceStatus = WaterHeaterStatusModel.fromJson( + event.devicesUuid.first, status.status); + + emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); + } catch (e) { + emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); + } + } + + FutureOr _batchControlWaterHeater(ControlWaterHeaterBatchEvent event, + Emitter emit) async { + if (state is WaterHeaterDeviceStatusLoaded) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + + emit(currentState.copyWith( + status: deviceStatus, + )); + + final success = await _runDebounce( + deviceId: event.devicesUuid, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: true, + ); + + if (success) { + if (event.code == "switch_1") { + emit(currentState.copyWith( + status: deviceStatus, + )); + } + } else { + _updateLocalValue(event.code, oldValue); + } + } + } } diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index bc0909ab..e2e718ae 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -52,15 +52,6 @@ final class WaterHeaterFetchStatusEvent extends WaterHeaterEvent { List get props => [deviceId]; } -final class WaterHeaterFetchBatchStatusEvent extends WaterHeaterEvent { - final String deviceId; - - const WaterHeaterFetchBatchStatusEvent(this.deviceId); - - @override - List get props => [deviceId]; -} - final class DecrementCountdownEvent extends WaterHeaterEvent {} final class AddScheduleEvent extends WaterHeaterEvent { @@ -106,7 +97,8 @@ final class UpdateScheduleEntryEvent extends WaterHeaterEvent { }); @override - List get props => [category, functionOn, deviceId, scheduleId, index]; + List get props => + [category, functionOn, deviceId, scheduleId, index]; } class GetSchedulesEvent extends WaterHeaterEvent { @@ -165,3 +157,26 @@ class UpdateFunctionOnEvent extends WaterHeaterEvent { @override List get props => [isOn]; } + +class FetchWaterHeaterBatchStatusEvent extends WaterHeaterEvent { + final List devicesUuid; + const FetchWaterHeaterBatchStatusEvent({required this.devicesUuid}); + + @override + List get props => [devicesUuid]; +} + +class ControlWaterHeaterBatchEvent extends WaterHeaterEvent { + final List devicesUuid; + final String code; + final dynamic value; + + const ControlWaterHeaterBatchEvent({ + required this.devicesUuid, + required this.code, + required this.value, + }); + + @override + List get props => [devicesUuid, code, value]; +} diff --git a/lib/pages/device_managment/water_heater/view/water_heater_batch_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_batch_control.dart new file mode 100644 index 00000000..cc62adfd --- /dev/null +++ b/lib/pages/device_managment/water_heater/view/water_heater_batch_control.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.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/water_heater_status_model.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class WaterHEaterBatchControlView extends StatelessWidget + with HelperResponsiveLayout { + const WaterHEaterBatchControlView({super.key, required this.deviceIds}); + + final List deviceIds; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => WaterHeaterBloc() + ..add(FetchWaterHeaterBatchStatusEvent(devicesUuid: deviceIds)), + child: BlocBuilder( + builder: (context, state) { + if (state is WaterHeaterLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is WaterHeaterDeviceStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is WaterHeaterBatchFailedState) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls( + BuildContext context, WaterHeaterStatusModel status) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return SizedBox( + child: 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, + ), + children: [ + ToggleWidget( + value: status.heaterSwitch, + code: 'switch_1', + deviceId: deviceIds.first, + label: 'Water Heater', + icon: Assets.waterHeater, + onChange: (value) { + context.read().add( + ControlWaterHeaterBatchEvent( + devicesUuid: deviceIds, + code: 'switch_1', + value: value, + ), + ); + }, + ), + FirmwareUpdateWidget( + deviceId: deviceIds.first, + version: 12, + ), + FactoryResetWidget( + callFactoryReset: () {}, + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart index c3f9d360..57f7444d 100644 --- a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart +++ b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart @@ -12,9 +12,9 @@ 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'; -class WaterHeaterDeviceControl extends StatelessWidget +class WaterHeaterDeviceControlView extends StatelessWidget with HelperResponsiveLayout { - const WaterHeaterDeviceControl({super.key, required this.device}); + const WaterHeaterDeviceControlView({super.key, required this.device}); final AllDevicesModel device; diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 719cc21c..4ad6ba2f 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -163,4 +163,7 @@ class Assets { //assets/icons/open_close_records.svg static const String openCloseRecords = 'assets/icons/open_close_records.svg'; + + //assets/icons/water_heater.svg + static const String waterHeater = 'assets/icons/water_heater.svg'; } From 6e3ad984e1f1577648a54840ec118267f85625ad Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 23 Sep 2024 09:22:32 +0300 Subject: [PATCH 38/65] change timer picker theme color --- .../helper/add_schedule_dialog_helper.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart index 68e364d7..c6cc51df 100644 --- a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -79,6 +79,16 @@ class ScheduleDialogHelper { context: context, initialTime: state.selectedTime ?? TimeOfDay.now(), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: const ColorScheme.light( + primary: ColorsManager.primaryColor, + ), + ), + child: child!, + ); + }, ); if (time != null) { bloc.add(UpdateSelectedTimeEvent(time)); From f3f63692371dcd1c431147c660e147524573daaa Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 24 Sep 2024 00:51:13 +0300 Subject: [PATCH 39/65] push schedule apis --- .../water_heater/bloc/water_heater_bloc.dart | 14 +- .../water_heater/bloc/water_heater_event.dart | 7 +- .../helper/add_schedule_dialog_helper.dart | 33 ++-- .../water_heater/models/schedule_entry.dart | 93 +++++++++-- .../water_heater/models/schedule_model.dart | 39 +++-- .../water_heater/widgets/schedual_view.dart | 13 +- .../widgets/schedule_mode_selector.dart | 2 +- .../widgets/schedule_row_widget.dart | 144 +++++++++--------- .../water_heater/widgets/schedule_table.dart | 67 ++++++-- lib/services/devices_mang_api.dart | 13 +- lib/utils/format_date_time.dart | 13 +- 11 files changed, 273 insertions(+), 165 deletions(-) diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index 2e84c011..daca7632 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -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 { emit(ScheduleLoadingState()); try { - // List schedules = await DevicesManagementApi() - // .getDeviceSchedules(deviceStatus.uuid, event.category); - - List schedules = const []; + List schedules = await DevicesManagementApi() + .getDeviceSchedules(deviceStatus.uuid, event.category); emit(WaterHeaterDeviceStatusLoaded( deviceStatus, @@ -513,7 +512,7 @@ class WaterHeaterBloc extends Bloc { 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 { .addScheduleRecord(newSchedule, currentState.status.uuid); if (success) { - final updatedSchedules = - List.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.')); diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index e2e718ae..9291e7c3 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -83,22 +83,21 @@ final class DeleteScheduleEvent extends WaterHeaterEvent { final class UpdateScheduleEntryEvent extends WaterHeaterEvent { final bool functionOn; - final String category; +// final String category; final String deviceId; final int index; final String scheduleId; const UpdateScheduleEntryEvent({ required this.functionOn, - required this.category, + // required this.category, required this.deviceId, required this.scheduleId, required this.index, }); @override - List get props => - [category, functionOn, deviceId, scheduleId, index]; + List get props => [functionOn, deviceId, scheduleId]; } class GetSchedulesEvent extends WaterHeaterEvent { diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart index c6cc51df..97340521 100644 --- a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -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 _convertDaysStringToBooleans(List selectedDays) { @@ -233,9 +234,9 @@ class ScheduleDialogHelper { value: true, groupValue: isOn, onChanged: (bool? value) { - context - .read() - .add(const UpdateFunctionOnEvent(true)); + // context + // .read() + // .add(const UpdateFunctionOnEvent(true)); }, ), const Text('On'), @@ -244,9 +245,9 @@ class ScheduleDialogHelper { value: false, groupValue: isOn, onChanged: (bool? value) { - context - .read() - .add(const UpdateFunctionOnEvent(false)); + // context + // .read() + // .add(const UpdateFunctionOnEvent(false)); }, ), const Text('Off'), diff --git a/lib/pages/device_managment/water_heater/models/schedule_entry.dart b/lib/pages/device_managment/water_heater/models/schedule_entry.dart index 8b79f563..ca14bf51 100644 --- a/lib/pages/device_managment/water_heater/models/schedule_entry.dart +++ b/lib/pages/device_managment/water_heater/models/schedule_entry.dart @@ -1,19 +1,80 @@ -// import 'package:flutter/material.dart'; +import 'dart:convert'; -// class ScheduleEntry { -// final List 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)'; -// } \ No newline at end of file +class ScheduleEntry { + final String category; + final String time; + final Status function; + final List 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? days, + }) { + return ScheduleEntry( + category: category ?? this.category, + time: time ?? this.time, + function: function ?? this.function, + days: days ?? this.days, + ); + } + + Map toMap() { + return { + 'category': category, + 'time': time, + 'function': function.toMap(), + 'days': days, + }; + } + + factory ScheduleEntry.fromMap(Map map) { + return ScheduleEntry( + category: map['category'] ?? '', + time: map['time'] ?? '', + function: Status.fromMap(map['function']), + days: List.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; + } +} diff --git a/lib/pages/device_managment/water_heater/models/schedule_model.dart b/lib/pages/device_managment/water_heater/models/schedule_model.dart index 7e9410be..3d05a3e0 100644 --- a/lib/pages/device_managment/water_heater/models/schedule_model.dart +++ b/lib/pages/device_managment/water_heater/models/schedule_model.dart @@ -8,26 +8,31 @@ class ScheduleModel { final String category; final String time; final Status function; - final List days; - final TimeOfDay? timeOfDay; + final List days; final List? 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 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.from(map['days']), - timeOfDay: - parseTimeOfDay(map['time']), - selectedDays: - parseSelectedDays(map['days']), + days: List.from(map['days'].map((e) => e.toString())), + timezoneId: map['timezoneId'] ?? '', + enable: map['enable'] ?? false, + selectedDays: parseSelectedDays( + List.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? days, - TimeOfDay? timeOfDay, List? 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; } } diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart index 09745eb3..739ae218 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart @@ -53,11 +53,10 @@ class _BuildScheduleViewState extends State { state: state, onAddSchedule: () => ScheduleDialogHelper.showAddScheduleDialog( - context, - schedule: null, - index: null, - isEdit: false - ), + context, + schedule: null, + index: null, + isEdit: false), ), if (state.scheduleMode == ScheduleModes.countdown || state.scheduleMode == ScheduleModes.inching) @@ -86,7 +85,9 @@ class _BuildScheduleViewState extends State { ); } if (state is WaterHeaterLoadingState) { - return const Center(child: CircularProgressIndicator()); + return const SizedBox( + height: 200, + child: Center(child: CircularProgressIndicator())); } return const SizedBox(); }, diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart b/lib/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart index 7afd96de..bb9ddc8f 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedule_mode_selector.dart @@ -72,7 +72,7 @@ class ScheduleModeSelector extends StatelessWidget { if (value == ScheduleModes.schedule) { context.read().add( GetSchedulesEvent( - category: 'kg', + category: 'switch_1', uuid: state.status.uuid, ), ); diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart b/lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart index 760b86c7..ee0805d6 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart @@ -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 selectedDays) { - final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; - List selectedDaysStr = []; - for (int i = 0; i < selectedDays.length; i++) { - if (selectedDays[i]) { - selectedDaysStr.add(days[i]); - } - } - return selectedDaysStr.join(', '); - } -} +// String _getSelectedDays(List selectedDays) { +// final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; +// List selectedDaysStr = []; +// for (int i = 0; i < selectedDays.length; i++) { +// if (selectedDays[i]) { +// selectedDaysStr.add(days[i]); +// } +// } +// return selectedDaysStr.join(', '); +// } +// } diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_table.dart b/lib/pages/device_managment/water_heater/widgets/schedule_table.dart index b997c7e0..19ec97a7 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedule_table.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedule_table.dart @@ -10,7 +10,7 @@ import 'package:syncrow_web/utils/format_date_time.dart'; import '../helper/add_schedule_dialog_helper.dart'; -class ScheduleTableWidget extends StatelessWidget { +class ScheduleTableWidget extends StatefulWidget { final WaterHeaterDeviceStatusLoaded state; const ScheduleTableWidget({ @@ -18,6 +18,26 @@ class ScheduleTableWidget extends StatelessWidget { required this.state, }); + @override + State createState() => _ScheduleTableWidgetState(); +} + +class _ScheduleTableWidgetState extends State { + late Map _enabledStates; + + @override + void initState() { + super.initState(); + _initializeEnabledStates(); + } + + void _initializeEnabledStates() { + _enabledStates = { + for (int i = 0; i < widget.state.schedules.length; i++) + i: widget.state.schedules[i].enable + }; + } + @override Widget build(BuildContext context) { return Column( @@ -107,14 +127,17 @@ class ScheduleTableWidget extends StatelessWidget { Widget _buildTableBody( WaterHeaterDeviceStatusLoaded state, BuildContext context) { - 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), - ], + 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, state), + ], + ), ), ); } @@ -134,19 +157,31 @@ 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 - ? const Icon(Icons.radio_button_checked, - color: ColorsManager.blueColor) - : const Icon(Icons.radio_button_unchecked)), + child: Radio( + value: true, + groupValue: _enabledStates[index], + onChanged: (bool? value) { + setState(() { + _enabledStates[index] = value!; + }); + context.read().add(UpdateScheduleEntryEvent( + index: index, + functionOn: value ?? false, + scheduleId: schedule.scheduleId, + deviceId: state.status.uuid, + )); + }, + ), + ), 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( diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index 5551bfe6..b22eedbb 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -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 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 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 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, diff --git a/lib/utils/format_date_time.dart b/lib/utils/format_date_time.dart index 470acb97..f26f4b36 100644 --- a/lib/utils/format_date_time.dart +++ b/lib/utils/format_date_time.dart @@ -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; + } } From d47b0f4c71cbdfc75f28b3b304bcbe5d3f82c727 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 24 Sep 2024 10:45:17 +0300 Subject: [PATCH 40/65] push schedules active enable api --- .../water_heater/bloc/water_heater_bloc.dart | 32 ++++++------ .../water_heater/bloc/water_heater_event.dart | 8 +-- .../helper/add_schedule_dialog_helper.dart | 25 +++++++--- .../water_heater/widgets/schedual_view.dart | 20 ++++++-- .../water_heater/widgets/schedule_table.dart | 49 +++++++------------ 5 files changed, 73 insertions(+), 61 deletions(-) diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index daca7632..9d24eb12 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -540,27 +540,27 @@ class WaterHeaterBloc extends Bloc { 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.from(currentState.schedules) - ..[event.index] = updatedSchedule; - emit(currentState.copyWith(schedules: updatedSchedules)); } else { emit(currentState); - // emit(const WaterHeaterFailedState(error: 'Failed to update schedule.')); + // emit(const WaterHeaterFailedState(error: 'Failed to update schedule.')); } } } @@ -572,20 +572,20 @@ class WaterHeaterBloc extends Bloc { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; - // emit(ScheduleLoadingState()); + // emit(ScheduleLoadingState()); bool success = await DevicesManagementApi() .deleteScheduleRecord(currentState.status.uuid, event.scheduleId); if (success) { - final updatedSchedules = - List.from(currentState.schedules) - ..removeAt(event.index); + final updatedSchedules = currentState.schedules + .where((schedule) => schedule.scheduleId != event.scheduleId) + .toList(); emit(currentState.copyWith(schedules: updatedSchedules)); } else { emit(currentState); - // emit(const WaterHeaterFailedState(error: 'Failed to delete schedule.')); + // emit(const WaterHeaterFailedState(error: 'Failed to delete schedule.')); } } } diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index 9291e7c3..d723f709 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -82,22 +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 get props => [functionOn, deviceId, scheduleId]; + List get props => [enable, deviceId, scheduleId]; } class GetSchedulesEvent extends WaterHeaterEvent { diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart index 97340521..2dd61f2f 100644 --- a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -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: [ @@ -221,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( @@ -234,9 +235,13 @@ class ScheduleDialogHelper { value: true, groupValue: isOn, onChanged: (bool? value) { - // context - // .read() - // .add(const UpdateFunctionOnEvent(true)); + if (isEdit == true) { + return; + } else { + context + .read() + .add(const UpdateFunctionOnEvent(true)); + } }, ), const Text('On'), @@ -245,9 +250,13 @@ class ScheduleDialogHelper { value: false, groupValue: isOn, onChanged: (bool? value) { - // context - // .read() - // .add(const UpdateFunctionOnEvent(false)); + if (isEdit == true) { + return; + } else { + context + .read() + .add(const UpdateFunctionOnEvent(false)); + } }, ), const Text('Off'), diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart index 739ae218..fc308e07 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart @@ -79,7 +79,9 @@ class _BuildScheduleViewState extends State { if (state.scheduleMode != ScheduleModes.countdown && state.scheduleMode != ScheduleModes.inching) ScheduleModeButtons( - onSave: () {}, + onSave: () { + Navigator.pop(context); + }, ), ], ); @@ -87,9 +89,21 @@ class _BuildScheduleViewState extends State { if (state is WaterHeaterLoadingState) { return const SizedBox( height: 200, - child: Center(child: CircularProgressIndicator())); + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ScheduleHeader(), + SizedBox( + height: 20, + ), + Center(child: CircularProgressIndicator()), + ], + )); } - return const SizedBox(); + return const SizedBox( + height: 200, + child: ScheduleHeader(), + ); }, ), ), diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_table.dart b/lib/pages/device_managment/water_heater/widgets/schedule_table.dart index 19ec97a7..c1694107 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedule_table.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedule_table.dart @@ -10,7 +10,7 @@ import 'package:syncrow_web/utils/format_date_time.dart'; import '../helper/add_schedule_dialog_helper.dart'; -class ScheduleTableWidget extends StatefulWidget { +class ScheduleTableWidget extends StatelessWidget { final WaterHeaterDeviceStatusLoaded state; const ScheduleTableWidget({ @@ -18,26 +18,6 @@ class ScheduleTableWidget extends StatefulWidget { required this.state, }); - @override - State createState() => _ScheduleTableWidgetState(); -} - -class _ScheduleTableWidgetState extends State { - late Map _enabledStates; - - @override - void initState() { - super.initState(); - _initializeEnabledStates(); - } - - void _initializeEnabledStates() { - _enabledStates = { - for (int i = 0; i < widget.state.schedules.length; i++) - i: widget.state.schedules[i].enable - }; - } - @override Widget build(BuildContext context) { return Column( @@ -88,7 +68,9 @@ class _ScheduleTableWidgetState extends State { ), child: _buildTableBody(state, context)); } - return const SizedBox(); + return const SizedBox( + height: 200, + ); }, ), ], @@ -162,20 +144,27 @@ class _ScheduleTableWidgetState extends State { return TableRow( children: [ Center( - child: Radio( - value: true, - groupValue: _enabledStates[index], - onChanged: (bool? value) { - setState(() { - _enabledStates[index] = value!; - }); + child: GestureDetector( + onTap: () { context.read().add(UpdateScheduleEntryEvent( index: index, - functionOn: value ?? false, + 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, + color: ColorsManager.grayColor, + ), + ), ), ), Center( From 6568b26cb1985d8fb632f3aa284129c9eddf4fc0 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 24 Sep 2024 11:01:45 +0300 Subject: [PATCH 41/65] push centered 2 gang switch --- .../view/wall_light_device_control.dart | 83 +++++++++---------- 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart b/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart index e72756eb..840d356e 100644 --- a/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart +++ b/lib/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart @@ -35,51 +35,46 @@ 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: [ + SizedBox( + width: 200, + child: ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceId, + label: 'Wall Light', + onChange: (value) { + context.read().add(TwoGangSwitchControl( + deviceId: deviceId, + code: 'switch_1', + value: value, + )); + }, + ), + ), + SizedBox( + width: 200, + child: ToggleWidget( + value: status.switch2, + code: 'switch_2', + deviceId: deviceId, + label: 'Ceiling Light', + onChange: (value) { + context.read().add(TwoGangSwitchControl( + deviceId: deviceId, + code: 'switch_2', + value: value, + )); + }, + ), + ), + ], ), - children: [ - ToggleWidget( - value: status.switch1, - code: 'switch_1', - deviceId: deviceId, - label: 'Wall Light', - onChange: (value) { - context.read().add(TwoGangSwitchControl( - deviceId: deviceId, - code: 'switch_1', - value: value, - )); - }, - ), - ToggleWidget( - value: status.switch2, - code: 'switch_2', - deviceId: deviceId, - label: 'Ceiling Light', - onChange: (value) { - context.read().add(TwoGangSwitchControl( - deviceId: deviceId, - code: 'switch_2', - value: value, - )); - }, - ), - ], ); } } From e690ddc23ac864f80f6cc2144cf5e4133e9ff7a8 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 24 Sep 2024 22:10:36 +0300 Subject: [PATCH 42/65] push selectAll bug, design and icons fixes --- lib/pages/common/custom_table.dart | 49 +++++++------------ .../ac/view/ac_device_batch_control.dart | 1 + .../batch_control_list/batch_fan_speed.dart | 1 + .../ac/view/control_list/ac_toggle.dart | 2 +- .../ac/view/control_list/fan_speed.dart | 1 + .../bloc/device_managment_bloc.dart | 43 ++++++++++++++++ .../bloc/device_managment_event.dart | 6 +++ .../widgets/device_managment_body.dart | 6 +++ .../shared/toggle_widget.dart | 9 ++-- lib/utils/format_date_time.dart | 4 +- 10 files changed, 85 insertions(+), 37 deletions(-) diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index b6829ec2..02f4e7ad 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -17,6 +17,7 @@ class DynamicTable extends StatefulWidget { final void Function(int, bool, dynamic)? onRowSelected; final List? initialSelectedIds; final int uuidIndex; + final Function(dynamic selectedRows)? onSelectionChanged; const DynamicTable({ super.key, required this.headers, @@ -32,6 +33,7 @@ class DynamicTable extends StatefulWidget { this.onRowSelected, this.initialSelectedIds, required this.uuidIndex, + this.onSelectionChanged, }); @override @@ -39,7 +41,7 @@ class DynamicTable extends StatefulWidget { } class _DynamicTableState extends State { - late List _selected; + late List _selectedRows; bool _selectAll = false; @override @@ -51,47 +53,30 @@ class _DynamicTableState extends State { @override void didUpdateWidget(DynamicTable oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.data != widget.data) { + if (oldWidget.data.length != widget.data.length) { _initializeSelection(); } } void _initializeSelection() { - if (widget.data.isEmpty) { - _selected = []; - _selectAll = false; - } else { - _selected = List.generate(widget.data.length, (index) { - // Check if the initialSelectedIds contains the deviceUuid - // uuidIndex is the index of the column containing the deviceUuid - final deviceUuid = widget.data[index][widget.uuidIndex]; - return widget.initialSelectedIds != null && - widget.initialSelectedIds!.contains(deviceUuid); - }); - _selectAll = _selected.every((element) => element == true); - } - } - - void _toggleRowSelection(int index) { - setState(() { - _selected[index] = !_selected[index]; - - if (widget.onRowSelected != null) { - widget.onRowSelected!(index, _selected[index], widget.data[index]); - } - }); + _selectedRows = List.filled(widget.data.length, false); + _selectAll = false; } void _toggleSelectAll(bool? value) { setState(() { _selectAll = value ?? false; - _selected = List.filled(widget.data.length, _selectAll); - for (int i = 0; i < widget.data.length; i++) { - if (widget.onRowSelected != null) { - widget.onRowSelected!(i, _selectAll, widget.data[i]); - } - } + _selectedRows = List.filled(widget.data.length, _selectAll); }); + widget.onSelectionChanged?.call(_selectedRows); + } + + void _toggleRowSelection(int index) { + setState(() { + _selectedRows[index] = !_selectedRows[index]; + _selectAll = _selectedRows.every((isSelected) => isSelected); + }); + widget.onSelectionChanged?.call(_selectedRows); } @override @@ -211,7 +196,7 @@ class _DynamicTableState extends State { alignment: Alignment.centerLeft, child: Center( child: Checkbox( - value: _selected[index], + value: _selectedRows[index], onChanged: (bool? value) { _toggleRowSelection(index); }, diff --git a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart index 987084fa..07dee6fd 100644 --- a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart @@ -46,6 +46,7 @@ class AcDeviceBatchControlView extends StatelessWidget ), children: [ ToggleWidget( + icon: Assets.ac, deviceId: devicesIds.first, code: 'switch', value: state.status.acSwitch, diff --git a/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart b/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart index b48d602c..11882473 100644 --- a/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart +++ b/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart @@ -40,6 +40,7 @@ class BatchFanSpeedControl extends StatelessWidget { value == FanSpeeds.low), ], ), + const SizedBox(height: 8), Wrap( runSpacing: 8, spacing: 8, diff --git a/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart b/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart index b9bf6014..81012b39 100644 --- a/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart +++ b/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart @@ -43,7 +43,7 @@ class AcToggle extends StatelessWidget { child: Container( color: ColorsManager.whiteColors, child: SvgPicture.asset( - icon ?? Assets.acDevice, + icon ?? Assets.lightPulp, width: 60, height: 60, fit: BoxFit.cover, diff --git a/lib/pages/device_managment/ac/view/control_list/fan_speed.dart b/lib/pages/device_managment/ac/view/control_list/fan_speed.dart index c1e95d53..8612c689 100644 --- a/lib/pages/device_managment/ac/view/control_list/fan_speed.dart +++ b/lib/pages/device_managment/ac/view/control_list/fan_speed.dart @@ -40,6 +40,7 @@ class FanSpeedControl extends StatelessWidget { value == FanSpeeds.low), ], ), + const SizedBox(height: 8), Wrap( runSpacing: 8, spacing: 8, diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart index 28a7047a..fc066335 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart @@ -25,6 +25,7 @@ class DeviceManagementBloc on(_onSelectDevice); on(_onResetFilters); on(_onResetSelectedDevices); + on(_onUpdateSelection); } Future _onFetchDevices( @@ -172,6 +173,48 @@ class DeviceManagementBloc } } + void _onUpdateSelection( + UpdateSelection event, Emitter emit) { + List selectedDevices = []; + List devicesToSelectFrom = []; + + if (state is DeviceManagementLoaded) { + devicesToSelectFrom = (state as DeviceManagementLoaded).devices; + } else if (state is DeviceManagementFiltered) { + devicesToSelectFrom = (state as DeviceManagementFiltered).filteredDevices; + } + + for (int i = 0; i < event.selectedRows.length; i++) { + if (event.selectedRows[i]) { + selectedDevices.add(devicesToSelectFrom[i]); + } + } + + if (state is DeviceManagementLoaded) { + final loadedState = state as DeviceManagementLoaded; + emit(DeviceManagementLoaded( + devices: loadedState.devices, + selectedIndex: loadedState.selectedIndex, + onlineCount: loadedState.onlineCount, + offlineCount: loadedState.offlineCount, + lowBatteryCount: loadedState.lowBatteryCount, + selectedDevice: selectedDevices, + isControlButtonEnabled: _checkIfControlButtonEnabled(selectedDevices), + )); + } else if (state is DeviceManagementFiltered) { + final filteredState = state as DeviceManagementFiltered; + emit(DeviceManagementFiltered( + filteredDevices: filteredState.filteredDevices, + selectedIndex: filteredState.selectedIndex, + onlineCount: filteredState.onlineCount, + offlineCount: filteredState.offlineCount, + lowBatteryCount: filteredState.lowBatteryCount, + selectedDevice: selectedDevices, + isControlButtonEnabled: _checkIfControlButtonEnabled(selectedDevices), + )); + } + } + bool _checkIfControlButtonEnabled(List selectedDevices) { if (selectedDevices.length > 1) { final productTypes = diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_event.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_event.dart index 16a2c076..bd27f2a6 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_managment_event.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_event.dart @@ -54,3 +54,9 @@ class SelectDevice extends DeviceManagementEvent { class ResetFilters extends DeviceManagementEvent {} class ResetSelectedDevices extends DeviceManagementEvent {} + +class UpdateSelection extends DeviceManagementEvent { + final List selectedRows; + + const UpdateSelection(this.selectedRows); +} diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index d2f5f1de..884927e4 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -120,6 +120,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { borderRadius: 9, child: Text( buttonLabel, + textAlign: TextAlign.center, style: TextStyle( fontSize: 12, color: isControlButtonEnabled @@ -178,6 +179,11 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { (device.updateTime ?? 0) * 1000)), ]; }).toList(), + onSelectionChanged: (selectedRows) { + context + .read() + .add(UpdateSelection(selectedRows)); + }, initialSelectedIds: context .read() .selectedDevices diff --git a/lib/pages/device_managment/shared/toggle_widget.dart b/lib/pages/device_managment/shared/toggle_widget.dart index 5f88b106..3115d609 100644 --- a/lib/pages/device_managment/shared/toggle_widget.dart +++ b/lib/pages/device_managment/shared/toggle_widget.dart @@ -47,12 +47,15 @@ class ToggleWidget extends StatelessWidget { ) : ClipOval( child: Container( + height: 60, + width: 60, + padding: const EdgeInsets.all(8), color: ColorsManager.whiteColors, child: SvgPicture.asset( icon ?? Assets.lightPulp, - width: 60, - height: 60, - fit: BoxFit.cover, + width: 35, + height: 35, + fit: BoxFit.contain, ), )), Text( diff --git a/lib/utils/format_date_time.dart b/lib/utils/format_date_time.dart index f26f4b36..98d0eb5e 100644 --- a/lib/utils/format_date_time.dart +++ b/lib/utils/format_date_time.dart @@ -21,8 +21,10 @@ String formatTimeOfDayToISO(TimeOfDay time, {DateTime? currentDate}) { time.hour, time.minute, ); + // Convert DateTime to Unix timestamp (in seconds) + final unixTimestamp = dateTime.millisecondsSinceEpoch ~/ 1000; - return dateTime.toUtc().toIso8601String(); + return unixTimestamp.toString(); } String formatIsoStringToTime(String isoString, BuildContext context) { From 58938d459812aa98dc6c4170527072ef120d6ae2 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Wed, 25 Sep 2024 00:23:47 +0300 Subject: [PATCH 43/65] Added syncrow icons, changed the description, increased the width of the control button --- README.md | 2 - lib/pages/common/buttons/default_button.dart | 19 +++--- .../widgets/device_managment_body.dart | 54 +++++++----------- pubspec.yaml | 2 +- web/favicon.png | Bin 917 -> 1689 bytes web/icons/Icon-192.png | Bin 5292 -> 7830 bytes web/icons/Icon-512.png | Bin 8252 -> 27031 bytes web/icons/Icon-maskable-192.png | Bin 5594 -> 7830 bytes web/icons/Icon-maskable-512.png | Bin 20998 -> 27031 bytes web/index.html | 2 +- web/manifest.json | 2 +- 11 files changed, 31 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index e6fef581..745fe6f0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # syncrow_web -A new Flutter project. - ## Getting Started This project is a starting point for a Flutter application. diff --git a/lib/pages/common/buttons/default_button.dart b/lib/pages/common/buttons/default_button.dart index 77eb9bd7..4ff89d74 100644 --- a/lib/pages/common/buttons/default_button.dart +++ b/lib/pages/common/buttons/default_button.dart @@ -42,39 +42,36 @@ class DefaultButton extends StatelessWidget { ? null : customButtonStyle ?? ButtonStyle( - textStyle: MaterialStateProperty.all( + textStyle: WidgetStateProperty.all( customTextStyle ?? Theme.of(context).textTheme.bodySmall!.copyWith( - fontSize: 13, - color: foregroundColor, - fontWeight: FontWeight.normal), + fontSize: 13, color: foregroundColor, fontWeight: FontWeight.normal), ), - foregroundColor: MaterialStateProperty.all( + foregroundColor: WidgetStateProperty.all( isSecondary ? Colors.black : enabled ? foregroundColor ?? Colors.white : Colors.black, ), - backgroundColor: MaterialStateProperty.resolveWith( - (Set states) { + backgroundColor: WidgetStateProperty.resolveWith((Set states) { return enabled ? backgroundColor ?? ColorsManager.primaryColor : Colors.black.withOpacity(0.2); }), - shape: MaterialStateProperty.all( + shape: WidgetStateProperty.all( RoundedRectangleBorder( side: BorderSide(color: borderColor ?? Colors.transparent), borderRadius: BorderRadius.circular(borderRadius ?? 20), ), ), - fixedSize: MaterialStateProperty.all( + fixedSize: WidgetStateProperty.all( const Size.fromHeight(50), ), - padding: MaterialStateProperty.all( + padding: WidgetStateProperty.all( EdgeInsets.all(padding ?? 10), ), - minimumSize: MaterialStateProperty.all( + minimumSize: WidgetStateProperty.all( const Size.fromHeight(50), ), ), diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index 884927e4..55e4e291 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -37,8 +37,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { offlineCount = state.offlineCount; lowBatteryCount = state.lowBatteryCount; isControlButtonEnabled = state.isControlButtonEnabled; - selectedDevices = state.selectedDevice ?? - context.read().selectedDevices; + selectedDevices = + state.selectedDevice ?? context.read().selectedDevices; } else if (state is DeviceManagementFiltered) { devicesToShow = state.filteredDevices; selectedIndex = state.selectedIndex; @@ -46,8 +46,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { offlineCount = state.offlineCount; lowBatteryCount = state.lowBatteryCount; isControlButtonEnabled = state.isControlButtonEnabled; - selectedDevices = state.selectedDevice ?? - context.read().selectedDevices; + selectedDevices = + state.selectedDevice ?? context.read().selectedDevices; } else if (state is DeviceManagementInitial) { devicesToShow = []; selectedIndex = 0; @@ -61,15 +61,13 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { 'Low Battery ($lowBatteryCount)', ]; - final buttonLabel = - (selectedDevices.length > 1) ? 'Batch Control' : 'Control'; + final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control'; return Column( children: [ Container( - padding: isLargeScreenSize(context) - ? const EdgeInsets.all(30) - : const EdgeInsets.all(15), + padding: + isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -78,9 +76,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { tabs: tabs, selectedIndex: selectedIndex, onTabChanged: (index) { - context - .read() - .add(SelectedFilterChanged(index)); + context.read().add(SelectedFilterChanged(index)); }, ), const SizedBox(height: 20), @@ -88,7 +84,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { const SizedBox(height: 12), Container( height: 45, - width: 100, + width: 125, decoration: containerDecoration, child: Center( child: DefaultButton( @@ -102,14 +98,12 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { ), ); } else if (selectedDevices.length > 1) { - final productTypes = selectedDevices - .map((device) => device.productType) - .toSet(); + final productTypes = + selectedDevices.map((device) => device.productType).toSet(); if (productTypes.length == 1) { showDialog( context: context, - builder: (context) => - DeviceBatchControlDialog( + builder: (context) => DeviceBatchControlDialog( devices: selectedDevices, ), ); @@ -123,9 +117,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { textAlign: TextAlign.center, style: TextStyle( fontSize: 12, - color: isControlButtonEnabled - ? Colors.white - : Colors.grey, + color: isControlButtonEnabled ? Colors.white : Colors.grey, ), ), ), @@ -144,9 +136,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { cellDecoration: containerDecoration, onRowSelected: (index, isSelected, row) { final selectedDevice = devicesToShow[index]; - context - .read() - .add(SelectDevice(selectedDevice)); + context.read().add(SelectDevice(selectedDevice)); }, withCheckBox: true, size: context.screenSize, @@ -169,20 +159,16 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { device.uuid ?? '', device.unit?.name ?? '', device.room?.name ?? '', - device.batteryLevel != null - ? '${device.batteryLevel}%' - : '-', - formatDateTime(DateTime.fromMillisecondsSinceEpoch( - (device.createTime ?? 0) * 1000)), + device.batteryLevel != null ? '${device.batteryLevel}%' : '-', + formatDateTime( + DateTime.fromMillisecondsSinceEpoch((device.createTime ?? 0) * 1000)), device.online == true ? 'Online' : 'Offline', - formatDateTime(DateTime.fromMillisecondsSinceEpoch( - (device.updateTime ?? 0) * 1000)), + formatDateTime( + DateTime.fromMillisecondsSinceEpoch((device.updateTime ?? 0) * 1000)), ]; }).toList(), onSelectionChanged: (selectedRows) { - context - .read() - .add(UpdateSelection(selectedRows)); + context.read().add(UpdateSelection(selectedRows)); }, initialSelectedIds: context .read() diff --git a/pubspec.yaml b/pubspec.yaml index 8b327340..dfeb1ff9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: syncrow_web -description: "A new Flutter project." +description: "Smart Home Solutions" # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev diff --git a/web/favicon.png b/web/favicon.png index 8aaa46ac1ae21512746f852a42ba87e4165dfdd1..7bf7ebbfbf066a0eb30b8f9b8a3bf739e6ee5241 100644 GIT binary patch delta 1674 zcmV;526g$B2bm3!BYy-AX+uL$YePpvZ)|UJQ*dEpWk+RhWpZg_Qb$4n062|}Rb6Nt zRTMs(xw9)I&V$sZrjic+HI%e$QqhFk=>BE1ZQLxAY_vg;$?V;oX(lt{{G@3qJ`_O^ z!KmPq;Qw2}Cn0L9?Tam0xTM?E#=eVv&ZJd#5#1reb5bk&@C&Dl`54c0UP7OzuN8ZAjaO;_P6){ zXX~y|M*)c4h1je($|B-r0R9!lvR!}#LHo!F*JAhy#5-)9AmS3kwE$Nbo)7S{&zX@j zh+jo4D5{M9#DCj}2j?U9S_B7k@Y{|&HK?sABFja|HmjOWBh$Tu`+v4Y-D||#gHMmM zFntu?1Gx5=Dsqof9u;;w>9fD%vK70 zx^G*=NAo>SWIm-WRcv9iqoP?gR{8=T%K&}3c)JK^23!2(_sKt!YsvSLA0~g}-sC>v z&VO^Ca-VVEfXH3oE^=RTUvVFEpGRucdG9o9?ysY<3g&5-QQV(GTG%Z-C}f2_!o$L3 zOHb$(hJ*=WFM4)2s}qeEUAqRyuy16meXM{NsdMaZth3hPKU7AR1}A)`#NoGw=04e|g00;m9hiL!= z000010000Q0000000N)_00aO40096101%)900aO40096101yBG000-AQ^)`S0s=`y zK~y*qT~j?vTu~4`bJxm{h`UJKw|`1N0`UhVl8}F(f{j=h3)9=hN+F4V!zNgmKrn?O z{(_)_g}n;_6>MVcvQ2XD5(XakVHJP)#c0hND`3GKwc>3_gN#6r_8R)WsUh){a>GQ`FCLE}$G zJw$N3Tqc`!a=yPv0_UdTWreq|aG~Zn#xbzg2QivGD!-LJnDB@Chq%U_-URiJ93gGY z*7PVU_k5Y>5NCa!1MDTzz_T5`CCr~kZiecnH8IRGS}C}m4!9;&^(5O`Fm9HA>3z{_ U?u4yk00000NkvXXt^-0~g7$7FhX4Qo literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png index b749bfef07473333cf1dd31e9eed89862a5d52aa..5170aa476b772fb89f80efca4977b6b94f5c34fe 100644 GIT binary patch literal 7830 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 diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png index 88cfd48dff1169879ba46840804b412fe02fefd6..c58953df96005326c9c197055291fd3c90b39dee 100644 GIT binary patch literal 27031 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 diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png index eb9b4d76e525556d5d89141648c724331630325d..5170aa476b772fb89f80efca4977b6b94f5c34fe 100644 GIT binary patch literal 7830 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png index d69c56691fbdb0b7efa65097c7cc1edac12a6d3e..c58953df96005326c9c197055291fd3c90b39dee 100644 GIT binary patch literal 27031 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 MPgg&ebxsLQ025IeI{*Lx diff --git a/web/index.html b/web/index.html index b3fc8dfc..a8b2aa25 100644 --- a/web/index.html +++ b/web/index.html @@ -18,7 +18,7 @@ - + diff --git a/web/manifest.json b/web/manifest.json index dc91e8ad..b037b66b 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -5,7 +5,7 @@ "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", - "description": "A new Flutter project.", + "description": "Smart Home Solutions", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ From 92d32e4d84863a4a3e4dcccd27548f3a654f03c7 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 30 Sep 2024 19:55:25 +0300 Subject: [PATCH 44/65] push bug fixes --- assets/icons/ac_lock.svg | 5 ++++ .../ac/view/ac_device_batch_control.dart | 5 ++-- .../ac/view/ac_device_control.dart | 4 ++-- .../ac/view/control_list/ac_toggle.dart | 23 +++++++++++-------- .../shared/device_control_dialog.dart | 13 ++++++++--- .../helper/add_schedule_dialog_helper.dart | 21 ++++++++--------- .../water_heater/widgets/schedual_view.dart | 1 + lib/utils/constants/assets.dart | 3 +++ 8 files changed, 46 insertions(+), 29 deletions(-) create mode 100644 assets/icons/ac_lock.svg diff --git a/assets/icons/ac_lock.svg b/assets/icons/ac_lock.svg new file mode 100644 index 00000000..9402fe6b --- /dev/null +++ b/assets/icons/ac_lock.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart index 07dee6fd..9331072d 100644 --- a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart @@ -46,7 +46,7 @@ class AcDeviceBatchControlView extends StatelessWidget ), children: [ ToggleWidget( - icon: Assets.ac, + icon: Assets.acLock, deviceId: devicesIds.first, code: 'switch', value: state.status.acSwitch, @@ -80,8 +80,7 @@ class AcDeviceBatchControlView extends StatelessWidget code: 'child_lock', value: state.status.childLock, label: 'Child Lock', - icon: - state.status.childLock ? Assets.childLock : Assets.unlock, + icon: state.status.childLock ? Assets.unlock : Assets.acLock, onChange: (value) { context.read().add(AcBatchControlEvent( devicesIds: devicesIds, diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index 7ae6f973..2283f6ad 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -46,6 +46,7 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { value: state.status.acSwitch, code: 'switch', deviceId: device.uuid!, + icon: Assets.ac, ), CurrentTemp( currentTemp: state.status.currentTemp, @@ -68,8 +69,7 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { code: 'child_lock', deviceId: device.uuid!, description: 'Child Lock', - icon: - state.status.childLock ? Assets.childLock : Assets.unlock, + icon: state.status.childLock ? Assets.unlock : Assets.acLock, ), ], ); diff --git a/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart b/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart index 81012b39..e709a6b4 100644 --- a/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart +++ b/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart @@ -39,16 +39,21 @@ class AcToggle extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, children: [ - ClipOval( - child: Container( - color: ColorsManager.whiteColors, - child: SvgPicture.asset( - icon ?? Assets.lightPulp, - width: 60, - height: 60, - fit: BoxFit.cover, + Container( + width: 60, + height: 60, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, ), - )), + padding: const EdgeInsets.all(8), + child: ClipOval( + child: SvgPicture.asset( + icon ?? Assets.lightPulp, + fit: BoxFit.contain, + ), + ), + ), SizedBox( height: 20, width: 35, diff --git a/lib/pages/device_managment/shared/device_control_dialog.dart b/lib/pages/device_managment/shared/device_control_dialog.dart index 77f52f62..f69c7c83 100644 --- a/lib/pages/device_managment/shared/device_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_control_dialog.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_cont import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/format_date_time.dart'; class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { final AllDevicesModel device; @@ -91,8 +92,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { ], ), TableRow(children: [ - _buildInfoRow('Virtual Address:', - 'Area - Street 1 - Building 1 - First Floor'), + _buildInfoRow('Virtual Address:', '${device.ip}'), const SizedBox.shrink(), ]), TableRow( @@ -103,7 +103,14 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { ), TableRow( children: [ - _buildInfoRow('Installation Date and Time:', '09/08/2024 13:30'), + _buildInfoRow( + 'Installation Date and Time:', + formatDateTime( + DateTime.fromMillisecondsSinceEpoch( + ((device.createTime ?? 0) * 1000), + ), + ), + ), const SizedBox.shrink(), ], ), diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart index 2dd61f2f..28a0fc32 100644 --- a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -11,7 +11,14 @@ class ScheduleDialogHelper { {ScheduleModel? schedule, int? index, bool? isEdit}) { final bloc = context.read(); - if (schedule != null) { + if (schedule == null) { + bloc.add(InitializeAddScheduleEvent( + selectedTime: null, + selectedDays: List.filled(7, false), + functionOn: false, + isEditing: false, + )); + } else { final time = _convertStringToTimeOfDay(schedule.time); final selectedDays = _convertDaysStringToBooleans(schedule.days); @@ -22,16 +29,6 @@ class ScheduleDialogHelper { 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( @@ -98,7 +95,7 @@ class ScheduleDialogHelper { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - state.selectedTime == null + state.selectedTime == null || schedule == null ? 'Time' : state.selectedTime!.format(context), style: context.textTheme.bodySmall!.copyWith( diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart index fc308e07..4febbaf5 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart @@ -24,6 +24,7 @@ class _BuildScheduleViewState extends State { @override Widget build(BuildContext context) { final bloc = BlocProvider.of(context); + return BlocProvider.value( value: bloc, child: Dialog( diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 4ad6ba2f..ee069c58 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -166,4 +166,7 @@ class Assets { //assets/icons/water_heater.svg static const String waterHeater = 'assets/icons/water_heater.svg'; + + //assets/icons/ac_lock.svg + static const String acLock = 'assets/icons/ac_lock.svg'; } From 9e5d5c0d842d85baf7c72065ce32f6b81c1b9a00 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 1 Oct 2024 00:27:03 +0300 Subject: [PATCH 45/65] push bug fixes --- lib/pages/common/buttons/default_button.dart | 10 +- .../ac/view/ac_device_batch_control.dart | 2 +- .../ac/view/ac_device_control.dart | 18 ++- .../widgets/device_managment_body.dart | 54 +++++--- .../gateway/view/gateway_view.dart | 4 +- .../view/main_door_control_view.dart | 4 +- .../shared/batch_control/factory_reset.dart | 122 ++++++++++++++---- .../shared/device_control_dialog.dart | 23 +++- .../water_heater/bloc/water_heater_bloc.dart | 1 + .../water_heater/bloc/water_heater_event.dart | 2 +- .../water_heater/bloc/water_heater_state.dart | 2 +- .../helper/add_schedule_dialog_helper.dart | 7 +- .../view/water_heater_device_control.dart | 1 + .../water_heater/widgets/schedual_view.dart | 13 +- .../water_heater/widgets/schedule_table.dart | 2 +- 15 files changed, 192 insertions(+), 73 deletions(-) diff --git a/lib/pages/common/buttons/default_button.dart b/lib/pages/common/buttons/default_button.dart index 4ff89d74..8c391ecb 100644 --- a/lib/pages/common/buttons/default_button.dart +++ b/lib/pages/common/buttons/default_button.dart @@ -18,6 +18,7 @@ class DefaultButton extends StatelessWidget { this.height, this.padding, this.borderColor, + this.elevation, }); final void Function()? onPressed; final Widget child; @@ -33,6 +34,7 @@ class DefaultButton extends StatelessWidget { final Color? backgroundColor; final Color? foregroundColor; final Color? borderColor; + final double? elevation; @override Widget build(BuildContext context) { @@ -45,7 +47,9 @@ class DefaultButton extends StatelessWidget { textStyle: WidgetStateProperty.all( customTextStyle ?? Theme.of(context).textTheme.bodySmall!.copyWith( - fontSize: 13, color: foregroundColor, fontWeight: FontWeight.normal), + fontSize: 13, + color: foregroundColor, + fontWeight: FontWeight.normal), ), foregroundColor: WidgetStateProperty.all( isSecondary @@ -54,7 +58,8 @@ class DefaultButton extends StatelessWidget { ? foregroundColor ?? Colors.white : Colors.black, ), - backgroundColor: WidgetStateProperty.resolveWith((Set states) { + backgroundColor: WidgetStateProperty.resolveWith( + (Set states) { return enabled ? backgroundColor ?? ColorsManager.primaryColor : Colors.black.withOpacity(0.2); @@ -74,6 +79,7 @@ class DefaultButton extends StatelessWidget { minimumSize: WidgetStateProperty.all( const Size.fromHeight(50), ), + elevation: WidgetStateProperty.all(elevation ?? 0), ), child: SizedBox( height: height ?? 50, diff --git a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart index 9331072d..94edf837 100644 --- a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart @@ -46,11 +46,11 @@ class AcDeviceBatchControlView extends StatelessWidget ), children: [ ToggleWidget( - icon: Assets.acLock, deviceId: devicesIds.first, code: 'switch', value: state.status.acSwitch, label: 'ThermoState', + icon: Assets.ac, onChange: (value) { context.read().add(AcBatchControlEvent( devicesIds: devicesIds, diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index 2283f6ad..c69583ae 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_toggl import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_temp.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; @@ -64,12 +65,21 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { code: 'level', deviceId: device.uuid!, ), - AcToggle( - value: state.status.childLock, - code: 'child_lock', + ToggleWidget( deviceId: device.uuid!, - description: 'Child Lock', + code: 'child_lock', + value: state.status.childLock, + label: 'Child Lock', icon: state.status.childLock ? Assets.unlock : Assets.acLock, + onChange: (value) { + context.read().add( + AcControlEvent( + deviceId: device.uuid!, + code: 'child_lock', + value: value, + ), + ); + }, ), ], ); diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index 55e4e291..46dc5bbd 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -37,8 +37,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { offlineCount = state.offlineCount; lowBatteryCount = state.lowBatteryCount; isControlButtonEnabled = state.isControlButtonEnabled; - selectedDevices = - state.selectedDevice ?? context.read().selectedDevices; + selectedDevices = state.selectedDevice ?? + context.read().selectedDevices; } else if (state is DeviceManagementFiltered) { devicesToShow = state.filteredDevices; selectedIndex = state.selectedIndex; @@ -46,8 +46,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { offlineCount = state.offlineCount; lowBatteryCount = state.lowBatteryCount; isControlButtonEnabled = state.isControlButtonEnabled; - selectedDevices = - state.selectedDevice ?? context.read().selectedDevices; + selectedDevices = state.selectedDevice ?? + context.read().selectedDevices; } else if (state is DeviceManagementInitial) { devicesToShow = []; selectedIndex = 0; @@ -61,13 +61,15 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { 'Low Battery ($lowBatteryCount)', ]; - final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control'; + final buttonLabel = + (selectedDevices.length > 1) ? 'Batch Control' : 'Control'; return Column( children: [ Container( - padding: - isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15), + padding: isLargeScreenSize(context) + ? const EdgeInsets.all(30) + : const EdgeInsets.all(15), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -76,7 +78,9 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { tabs: tabs, selectedIndex: selectedIndex, onTabChanged: (index) { - context.read().add(SelectedFilterChanged(index)); + context + .read() + .add(SelectedFilterChanged(index)); }, ), const SizedBox(height: 20), @@ -98,12 +102,14 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { ), ); } else if (selectedDevices.length > 1) { - final productTypes = - selectedDevices.map((device) => device.productType).toSet(); + final productTypes = selectedDevices + .map((device) => device.productType) + .toSet(); if (productTypes.length == 1) { showDialog( context: context, - builder: (context) => DeviceBatchControlDialog( + builder: (context) => + DeviceBatchControlDialog( devices: selectedDevices, ), ); @@ -117,7 +123,9 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { textAlign: TextAlign.center, style: TextStyle( fontSize: 12, - color: isControlButtonEnabled ? Colors.white : Colors.grey, + color: isControlButtonEnabled + ? Colors.white + : Colors.grey, ), ), ), @@ -136,7 +144,9 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { cellDecoration: containerDecoration, onRowSelected: (index, isSelected, row) { final selectedDevice = devicesToShow[index]; - context.read().add(SelectDevice(selectedDevice)); + context + .read() + .add(SelectDevice(selectedDevice)); }, withCheckBox: true, size: context.screenSize, @@ -154,21 +164,25 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { ], data: devicesToShow.map((device) { return [ - device.categoryName ?? '', device.name ?? '', + device.categoryName ?? '', device.uuid ?? '', device.unit?.name ?? '', device.room?.name ?? '', - device.batteryLevel != null ? '${device.batteryLevel}%' : '-', - formatDateTime( - DateTime.fromMillisecondsSinceEpoch((device.createTime ?? 0) * 1000)), + device.batteryLevel != null + ? '${device.batteryLevel}%' + : '-', + formatDateTime(DateTime.fromMillisecondsSinceEpoch( + (device.createTime ?? 0) * 1000)), device.online == true ? 'Online' : 'Offline', - formatDateTime( - DateTime.fromMillisecondsSinceEpoch((device.updateTime ?? 0) * 1000)), + formatDateTime(DateTime.fromMillisecondsSinceEpoch( + (device.updateTime ?? 0) * 1000)), ]; }).toList(), onSelectionChanged: (selectedRows) { - context.read().add(UpdateSelection(selectedRows)); + context + .read() + .add(UpdateSelection(selectedRows)); }, initialSelectedIds: context .read() diff --git a/lib/pages/device_managment/gateway/view/gateway_view.dart b/lib/pages/device_managment/gateway/view/gateway_view.dart index ad760a14..f6cc4682 100644 --- a/lib/pages/device_managment/gateway/view/gateway_view.dart +++ b/lib/pages/device_managment/gateway/view/gateway_view.dart @@ -70,8 +70,8 @@ class _DeviceItem extends StatelessWidget { Container( width: 60, height: 60, - decoration: const BoxDecoration( - shape: BoxShape.circle, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), color: ColorsManager.whiteColors, ), margin: const EdgeInsets.symmetric(horizontal: 4), diff --git a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart index 44b2fec3..5785a799 100644 --- a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart +++ b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart @@ -80,7 +80,9 @@ class MainDoorSensorControlView extends StatelessWidget icon: Assets.openCloseDoor, onTap: () {}, status: status.doorContactState, - textColor: ColorsManager.red, + textColor: status.doorContactState + ? ColorsManager.red + : ColorsManager.blackColor, paddingAmount: 8, ), IconNameStatusContainer( diff --git a/lib/pages/device_managment/shared/batch_control/factory_reset.dart b/lib/pages/device_managment/shared/batch_control/factory_reset.dart index 78dfc307..3153cd48 100644 --- a/lib/pages/device_managment/shared/batch_control/factory_reset.dart +++ b/lib/pages/device_managment/shared/batch_control/factory_reset.dart @@ -1,47 +1,113 @@ import 'package:flutter/material.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/shared/device_controls_container.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 FactoryResetWidget extends StatelessWidget { +class FactoryResetWidget extends StatefulWidget { const FactoryResetWidget({super.key, required this.callFactoryReset}); - final Null Function() callFactoryReset; + final Function() callFactoryReset; + + @override + State createState() => _FactoryResetWidgetState(); +} + +class _FactoryResetWidgetState extends State { + bool _showConfirmation = false; + + void _toggleConfirmation() { + setState(() { + _showConfirmation = !_showConfirmation; + }); + } @override Widget build(BuildContext context) { return DeviceControlsContainer( - child: GestureDetector( - onTap: callFactoryReset, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ClipOval( - child: Container( - color: ColorsManager.whiteColors, - height: 60, - width: 60, - child: Padding( - padding: const EdgeInsets.all(12.0), - child: SvgPicture.asset( - Assets.factoryReset, - fit: BoxFit.cover, + child: _showConfirmation + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Factory Reset', + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.blackColor, + ), ), - ), - )), - Text( - 'Factory Reset', - style: context.textTheme.titleMedium!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor, + Text( + 'Are you sure?', + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor, + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Flexible( + child: DefaultButton( + height: 20, + elevation: 0, + onPressed: _toggleConfirmation, + backgroundColor: ColorsManager.greyColor, + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium, + ), + ), + ), + const SizedBox(width: 8), + Flexible( + child: DefaultButton( + height: 20, + elevation: 0, + onPressed: widget.callFactoryReset, + backgroundColor: ColorsManager.red, + child: Text( + 'Reset', + style: context.textTheme.bodyMedium! + .copyWith(color: ColorsManager.whiteColors), + ), + ), + ), + ], + ), + ], + ) + : GestureDetector( + onTap: _toggleConfirmation, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ClipOval( + child: Container( + color: ColorsManager.whiteColors, + height: 60, + width: 60, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: SvgPicture.asset( + Assets.factoryReset, + fit: BoxFit.cover, + ), + ), + ), + ), + Text( + 'Factory Reset', + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + ), + ), + ], ), ), - ], - ), - ), ); } } diff --git a/lib/pages/device_managment/shared/device_control_dialog.dart b/lib/pages/device_managment/shared/device_control_dialog.dart index f69c7c83..acf1578c 100644 --- a/lib/pages/device_managment/shared/device_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_control_dialog.dart @@ -92,7 +92,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { ], ), TableRow(children: [ - _buildInfoRow('Virtual Address:', '${device.ip}'), + _buildInfoRow('Virtual Address:', device.ip ?? '-'), const SizedBox.shrink(), ]), TableRow( @@ -111,13 +111,30 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { ), ), ), - const SizedBox.shrink(), + _buildInfoRow( + 'Battery Level:', + device.batteryLevel != null + ? '${device.batteryLevel ?? 0}%' + : "-", + statusColor: device.batteryLevel != null + ? (device.batteryLevel! < 20 + ? ColorsManager.red + : ColorsManager.green) + : null, + ), ], ), TableRow( children: [ _buildInfoRow('Status:', 'Online', statusColor: Colors.green), - _buildInfoRow('Last Offline Date and Time:', '-'), + _buildInfoRow( + 'Last Offline Date and Time:', + formatDateTime( + DateTime.fromMillisecondsSinceEpoch( + ((device.activeTime ?? 0) * 1000), + ), + ), + ), ], ), ], diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index 9d24eb12..023faa7a 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -65,6 +65,7 @@ class WaterHeaterBloc extends Bloc { Emitter emit, ) { final currentState = state as WaterHeaterDeviceStatusLoaded; + emit(currentState.copyWith(selectedTime: event.selectedTime)); } diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index d723f709..4b9ec7a1 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -130,7 +130,7 @@ class InitializeAddScheduleEvent extends WaterHeaterEvent { } class UpdateSelectedTimeEvent extends WaterHeaterEvent { - final TimeOfDay selectedTime; + final TimeOfDay? selectedTime; const UpdateSelectedTimeEvent(this.selectedTime); diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart index 5ff5ba3d..c2df43c3 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_state.dart @@ -99,7 +99,7 @@ class WaterHeaterDeviceStatusLoaded extends WaterHeaterState { isInchingActive: isInchingActive ?? this.isInchingActive, schedules: schedules ?? this.schedules, selectedDays: selectedDays ?? this.selectedDays, - selectedTime: selectedTime ?? this.selectedTime, + selectedTime: selectedTime, functionOn: functionOn ?? this.functionOn, isEditing: isEditing ?? this.isEditing, ); diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart index 28a0fc32..4ccec509 100644 --- a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -12,6 +12,7 @@ class ScheduleDialogHelper { final bloc = context.read(); if (schedule == null) { + bloc.add((const UpdateSelectedTimeEvent(null))); bloc.add(InitializeAddScheduleEvent( selectedTime: null, selectedDays: List.filled(7, false), @@ -95,7 +96,7 @@ class ScheduleDialogHelper { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - state.selectedTime == null || schedule == null + state.selectedTime == null ? 'Time' : state.selectedTime!.format(context), style: context.textTheme.bodySmall!.copyWith( @@ -180,7 +181,7 @@ class ScheduleDialogHelper { } static List _convertDaysStringToBooleans(List selectedDays) { - final daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + final daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; List daysBoolean = List.filled(7, false); for (int i = 0; i < daysOfWeek.length; i++) { @@ -195,7 +196,7 @@ class ScheduleDialogHelper { static Widget _buildDayCheckboxes( BuildContext context, List selectedDays, {bool? isEdit}) { - final dayLabels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + final dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; return Row( children: List.generate(7, (index) { diff --git a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart index 57f7444d..40d3edb5 100644 --- a/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart +++ b/lib/pages/device_managment/water_heater/view/water_heater_device_control.dart @@ -62,6 +62,7 @@ class WaterHeaterDeviceControlView extends StatelessWidget deviceId: device.uuid!, code: 'switch_1', value: status.heaterSwitch, + icon: Assets.waterHeater, label: 'Water Heater', onChange: (value) { context.read().add(ToggleWaterHeaterEvent( diff --git a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart index 4febbaf5..9d4a2497 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedual_view.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedual_view.dart @@ -52,12 +52,13 @@ class _BuildScheduleViewState extends State { if (state.scheduleMode == ScheduleModes.schedule) ScheduleManagementUI( state: state, - onAddSchedule: () => - ScheduleDialogHelper.showAddScheduleDialog( - context, - schedule: null, - index: null, - isEdit: false), + onAddSchedule: () { + ScheduleDialogHelper.showAddScheduleDialog( + context, + schedule: null, + index: null, + isEdit: false); + }, ), if (state.scheduleMode == ScheduleModes.countdown || state.scheduleMode == ScheduleModes.inching) diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_table.dart b/lib/pages/device_managment/water_heater/widgets/schedule_table.dart index c1694107..18cbbe5a 100644 --- a/lib/pages/device_managment/water_heater/widgets/schedule_table.dart +++ b/lib/pages/device_managment/water_heater/widgets/schedule_table.dart @@ -210,7 +210,7 @@ class ScheduleTableWidget extends StatelessWidget { } String _getSelectedDays(List selectedDays) { - final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; + final days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; List selectedDaysStr = []; for (int i = 0; i < selectedDays.length; i++) { if (selectedDays[i]) { From 0d49ad5106b1947e1b21aa9fd0e2ecead998b38d Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 1 Oct 2024 00:32:17 +0300 Subject: [PATCH 46/65] change themostat widget --- .../device_managment/ac/view/ac_device_control.dart | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index c69583ae..c78ce58d 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -4,7 +4,6 @@ import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_mode.dart'; -import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_toggle.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_temp.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; @@ -43,11 +42,21 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { mainAxisSpacing: 12, ), children: [ - AcToggle( + ToggleWidget( + label: 'Thermostat', value: state.status.acSwitch, code: 'switch', deviceId: device.uuid!, icon: Assets.ac, + onChange: (value) { + context.read().add( + AcControlEvent( + deviceId: device.uuid!, + code: 'switch', + value: value, + ), + ); + }, ), CurrentTemp( currentTemp: state.status.currentTemp, From 9dbf4b0540151700c9e90c70d28ca3b817ba9477 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 2 Oct 2024 01:41:15 +0300 Subject: [PATCH 47/65] push batch door sensor, design bugs --- .../helper/route_controls_based_code.dart | 13 ++++- .../all_devices/models/devices_model.dart | 6 +++ .../widgets/device_managment_body.dart | 2 +- .../view/ceiling_sensor_batch_control.dart | 2 +- .../view/ceiling_sensor_controls.dart | 2 +- .../view/door_lock_batch_control_view.dart | 36 +++++++------- .../gateway/view/gateway_batch_control.dart | 49 +++++++++---------- .../bloc/main_door_sensor_bloc.dart | 19 +++++++ .../bloc/main_door_sensor_event.dart | 10 ++++ .../view/main_door_sensor_batch_view.dart | 47 ++++++++++++++++++ .../shared/batch_control/factory_reset.dart | 12 +++-- .../shared/device_batch_control_dialog.dart | 43 +++++++++++++++- .../shared/device_control_dialog.dart | 4 +- .../view/wall_sensor_batch_control.dart | 2 +- .../view/wall_sensor_conrtols.dart | 2 +- .../water_heater/bloc/water_heater_bloc.dart | 6 ++- 16 files changed, 195 insertions(+), 60 deletions(-) create mode 100644 lib/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 80a00094..068f8c89 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -12,6 +12,7 @@ import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_cont import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart'; @@ -76,7 +77,9 @@ mixin RouteControlsBasedCode { WPS: CPS: AC: - CUR: + CUR: + WH: + DS: */ Widget routeBatchControlsWidgets({required List devices}) { @@ -148,7 +151,13 @@ mixin RouteControlsBasedCode { .map((e) => e.uuid!) .toList(), ); - + case 'DS': + return MainDoorSensorBatchView( + devicesIds: devices + .where((e) => (e.productType == 'DS')) + .map((e) => e.uuid!) + .toList(), + ); default: return const SizedBox(); } diff --git a/lib/pages/device_managment/all_devices/models/devices_model.dart b/lib/pages/device_managment/all_devices/models/devices_model.dart index e81b7695..b706dad3 100644 --- a/lib/pages/device_managment/all_devices/models/devices_model.dart +++ b/lib/pages/device_managment/all_devices/models/devices_model.dart @@ -63,6 +63,7 @@ class AllDevicesModel { int? updateTime; String? uuid; int? batteryLevel; + String? productName; AllDevicesModel({ this.room, @@ -90,6 +91,7 @@ class AllDevicesModel { this.updateTime, this.uuid, this.batteryLevel, + this.productName, }); AllDevicesModel.fromJson(Map json) { room = (json['room'] != null && (json['room'] is Map)) @@ -121,6 +123,7 @@ class AllDevicesModel { updateTime = int.tryParse(json['updateTime']?.toString() ?? ''); uuid = json['uuid']?.toString(); batteryLevel = int.tryParse(json['battery']?.toString() ?? ''); + productName = json['productName']?.toString(); } String _getDefaultIcon(String? productType) { @@ -182,6 +185,7 @@ class AllDevicesModel { data['updateTime'] = updateTime; data['uuid'] = uuid; data['battery'] = batteryLevel; + data['productName'] = productName; return data; } @@ -214,6 +218,7 @@ class AllDevicesModel { other.timeZone == timeZone && other.updateTime == updateTime && other.uuid == uuid && + other.productName == productName && other.batteryLevel == batteryLevel; } @@ -243,6 +248,7 @@ class AllDevicesModel { timeZone.hashCode ^ updateTime.hashCode ^ uuid.hashCode ^ + productName.hashCode ^ batteryLevel.hashCode; } } diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index 46dc5bbd..242c8d5e 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -165,7 +165,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { data: devicesToShow.map((device) { return [ device.name ?? '', - device.categoryName ?? '', + device.productName ?? '', device.uuid ?? '', device.unit?.name ?? '', device.room?.name ?? '', diff --git a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart index a6e60c8f..d1dc54a2 100644 --- a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart +++ b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart @@ -73,7 +73,7 @@ class CeilingSensorBatchControlView extends StatelessWidget value: model.sensitivity.toDouble(), title: 'Sensitivity:', minValue: 1, - maxValue: 5, + maxValue: 10, steps: 1, action: (int value) { context.read().add( diff --git a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart index e2048ab4..ecf0afae 100644 --- a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart +++ b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart @@ -112,7 +112,7 @@ class CeilingSensorControlsView extends StatelessWidget value: model.sensitivity.toDouble(), title: 'Sensitivity:', minValue: 1, - maxValue: 5, + maxValue: 10, steps: 1, action: (int value) { context.read().add( diff --git a/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart b/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart index 4efd76fc..abbd48dd 100644 --- a/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart +++ b/lib/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart @@ -15,26 +15,24 @@ class DoorLockBatchControlView extends StatelessWidget @override Widget build(BuildContext context) { - final isExtraLarge = isExtraLargeScreenSize(context); - final isLarge = isLargeScreenSize(context); - final isMedium = isMediumScreenSize(context); - return SizedBox( - child: GridView( - padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20), - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: isLarge || isExtraLarge || isMedium ? 2 : 1, - mainAxisExtent: 140, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - ), - children: [ - FirmwareUpdateWidget( + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 170, + height: 140, + child: FirmwareUpdateWidget( deviceId: devicesIds.first, version: 12, ), - FactoryResetWidget( + ), + const SizedBox( + width: 12, + ), + SizedBox( + width: 170, + height: 140, + child: FactoryResetWidget( callFactoryReset: () { BlocProvider.of(context).add( DoorLockFactoryReset( @@ -44,8 +42,8 @@ class DoorLockBatchControlView extends StatelessWidget ); }, ), - ], - ), + ), + ], ); } } diff --git a/lib/pages/device_managment/gateway/view/gateway_batch_control.dart b/lib/pages/device_managment/gateway/view/gateway_batch_control.dart index 8679a78f..cb85b7d9 100644 --- a/lib/pages/device_managment/gateway/view/gateway_batch_control.dart +++ b/lib/pages/device_managment/gateway/view/gateway_batch_control.dart @@ -14,10 +14,6 @@ class GatewayBatchControlView extends StatelessWidget @override Widget build(BuildContext context) { - final isExtraLarge = isExtraLargeScreenSize(context); - final isLarge = isLargeScreenSize(context); - final isMedium = isMediumScreenSize(context); - return BlocProvider( create: (context) => GateWayBloc()..add(GatWayById(gatewayIds.first)), child: BlocBuilder( @@ -25,28 +21,31 @@ class GatewayBatchControlView extends StatelessWidget if (state is GatewayLoadingState) { return const Center(child: CircularProgressIndicator()); } else if (state is UpdateGatewayState) { - return GridView( - padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20), - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: isLarge || isExtraLarge || isMedium ? 2 : 1, - mainAxisExtent: 140, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - ), + return Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - FirmwareUpdateWidget(deviceId: gatewayIds.first, version: 2), - FactoryResetWidget( - callFactoryReset: () { - context.read().add( - GateWayFactoryReset( - deviceId: gatewayIds.first, - factoryReset: - FactoryResetModel(devicesUuid: gatewayIds), - ), - ); - }, + SizedBox( + width: 170, + height: 140, + child: FirmwareUpdateWidget( + deviceId: gatewayIds.first, version: 2)), + const SizedBox( + width: 12, + ), + SizedBox( + width: 170, + height: 140, + child: FactoryResetWidget( + callFactoryReset: () { + context.read().add( + GateWayFactoryReset( + deviceId: gatewayIds.first, + factoryReset: + FactoryResetModel(devicesUuid: gatewayIds), + ), + ); + }, + ), ), ], ); diff --git a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart index 71a4b699..933ce28b 100644 --- a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart +++ b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart @@ -15,6 +15,7 @@ class MainDoorSensorBloc on(_onControl); on(_onFetchBatchStatus); on(_fetchReports); + on(_factoryReset); } late MainDoorSensorStatusModel deviceStatus; @@ -137,4 +138,22 @@ class MainDoorSensorBloc _timer?.cancel(); return super.close(); } + + FutureOr _factoryReset(MainDoorSensorFactoryReset event, + Emitter emit) async { + emit(MainDoorSensorLoadingState()); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryReset, + event.deviceId, + ); + if (!response) { + emit(MainDoorSensorFailedState(error: 'Failed')); + } else { + add(MainDoorSensorFetchDeviceEvent(event.deviceId)); + } + } catch (e) { + emit(MainDoorSensorFailedState(error: e.toString())); + } + } } diff --git a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart index 40bec02c..c2864333 100644 --- a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart +++ b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart @@ -1,5 +1,7 @@ import 'package:equatable/equatable.dart'; +import '../../all_devices/models/factory_reset_model.dart'; + class MainDoorSensorEvent extends Equatable { @override List get props => []; @@ -61,3 +63,11 @@ class MainDoorSensorReportsEvent extends MainDoorSensorEvent { required this.from, required this.to}); } + +class MainDoorSensorFactoryReset extends MainDoorSensorEvent { + final String deviceId; + final FactoryResetModel factoryReset; + + MainDoorSensorFactoryReset( + {required this.deviceId, required this.factoryReset}); +} diff --git a/lib/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart b/lib/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart new file mode 100644 index 00000000..0cacc0be --- /dev/null +++ b/lib/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; + +class MainDoorSensorBatchView extends StatelessWidget { + const MainDoorSensorBatchView({super.key, required this.devicesIds}); + + final List devicesIds; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 170, + height: 140, + child: FirmwareUpdateWidget( + deviceId: devicesIds.first, + version: 12, + ), + ), + const SizedBox( + width: 12, + ), + SizedBox( + width: 170, + height: 140, + child: FactoryResetWidget( + callFactoryReset: () { + BlocProvider.of(context).add( + MainDoorSensorFactoryReset( + deviceId: devicesIds.first, + factoryReset: FactoryResetModel(devicesUuid: devicesIds), + ), + ); + }, + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/shared/batch_control/factory_reset.dart b/lib/pages/device_managment/shared/batch_control/factory_reset.dart index 3153cd48..ea8f833c 100644 --- a/lib/pages/device_managment/shared/batch_control/factory_reset.dart +++ b/lib/pages/device_managment/shared/batch_control/factory_reset.dart @@ -56,7 +56,11 @@ class _FactoryResetWidgetState extends State { backgroundColor: ColorsManager.greyColor, child: Text( 'Cancel', - style: context.textTheme.bodyMedium, + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), ), ), ), @@ -69,8 +73,10 @@ class _FactoryResetWidgetState extends State { backgroundColor: ColorsManager.red, child: Text( 'Reset', - style: context.textTheme.bodyMedium! - .copyWith(color: ColorsManager.whiteColors), + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.whiteColors, + fontWeight: FontWeight.w400, + fontSize: 12), ), ), ), diff --git a/lib/pages/device_managment/shared/device_batch_control_dialog.dart b/lib/pages/device_managment/shared/device_batch_control_dialog.dart index 8b87a964..84d822e6 100644 --- a/lib/pages/device_managment/shared/device_batch_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_batch_control_dialog.dart @@ -37,7 +37,7 @@ class DeviceBatchControlDialog extends StatelessWidget Column( children: [ Text( - devices.first.categoryName ?? 'Device Control', + getBatchDialogName(devices.first), style: context.textTheme.titleLarge!.copyWith( color: ColorsManager.dialogBlueTitle, fontWeight: FontWeight.bold, @@ -65,7 +65,7 @@ class DeviceBatchControlDialog extends StatelessWidget ), ), child: IconButton( - padding: EdgeInsets.all(1), + padding: const EdgeInsets.all(1), icon: const Icon( Icons.close, color: Colors.grey, @@ -92,3 +92,42 @@ class DeviceBatchControlDialog extends StatelessWidget ); } } + +String getBatchDialogName(AllDevicesModel device) { + /* +3G: + 1G: + 2G: + GW: + DL: + WPS: + CPS: + AC: + CUR: + WH: + */ + switch (device.productType) { + case '1G': + return "Smart Light Switch"; + case '2G': + return "2Gang Light"; + case '3G': + return "Living Room"; + case 'GW': + return "GateWay"; + case 'DL': + return "Door Lock"; + case 'WPS': + return "White Presence Sensor"; + case 'CPS': + return "Black Presence Sensor"; + case 'CUR': + return "Smart Curtains"; + case 'WH': + return "Smart Water Hater"; + case 'AC': + return "Smart AC"; + default: + return device.categoryName ?? 'Device Control'; + } +} diff --git a/lib/pages/device_managment/shared/device_control_dialog.dart b/lib/pages/device_managment/shared/device_control_dialog.dart index acf1578c..ba37203e 100644 --- a/lib/pages/device_managment/shared/device_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_control_dialog.dart @@ -87,7 +87,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { children: [ TableRow( children: [ - _buildInfoRow('Product Name:', device.categoryName ?? 'N/A'), + _buildInfoRow('Product Name:', device.productName ?? 'N/A'), _buildInfoRow('Device ID:', device.uuid ?? ''), ], ), @@ -131,7 +131,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { 'Last Offline Date and Time:', formatDateTime( DateTime.fromMillisecondsSinceEpoch( - ((device.activeTime ?? 0) * 1000), + ((device.updateTime ?? 0) * 1000), ), ), ), diff --git a/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart index 5e855208..66078d60 100644 --- a/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart +++ b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart @@ -96,7 +96,7 @@ class WallSensorBatchControlView extends StatelessWidget minValue: 10, maxValue: 10000, steps: 1, - description: 'hr', + description: 'sec', action: (int value) => context.read().add(WallSensorBatchControlEvent( deviceIds: devicesIds, diff --git a/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart b/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart index 4f789477..f0ff7591 100644 --- a/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart +++ b/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart @@ -135,7 +135,7 @@ class WallSensorControlsView extends StatelessWidget minValue: 10, maxValue: 10000, steps: 1, - description: 'hr', + description: 'sec', action: (int value) => context.read().add(WallSensorChangeValueEvent( code: 'no_one_time', diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index 023faa7a..0a5b2895 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -76,7 +76,8 @@ class WaterHeaterBloc extends Bloc { final currentState = state as WaterHeaterDeviceStatusLoaded; final updatedDays = List.from(currentState.selectedDays); updatedDays[event.index] = event.value; - emit(currentState.copyWith(selectedDays: updatedDays)); + emit(currentState.copyWith( + selectedDays: updatedDays, selectedTime: currentState.selectedTime)); } FutureOr _updateFunctionOn( @@ -84,7 +85,8 @@ class WaterHeaterBloc extends Bloc { Emitter emit, ) { final currentState = state as WaterHeaterDeviceStatusLoaded; - emit(currentState.copyWith(functionOn: event.isOn)); + emit(currentState.copyWith( + functionOn: event.isOn, selectedTime: currentState.selectedTime)); } FutureOr _updateScheduleEvent( From 30dd3a1ee2e892a3ba7bbb4b85aaadd59af3b0a9 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Wed, 2 Oct 2024 02:54:18 +0300 Subject: [PATCH 48/65] push ac schedule ac fake design --- assets/icons/ac_schedule.svg | 28 +++++++ .../ac/view/ac_device_batch_control.dart | 53 +++++++++++++ .../ac/view/ac_device_control.dart | 53 +++++++++++++ .../gateway/view/gateway_view.dart | 25 +++--- .../shared/toggle_widget.dart | 78 ++++++++++--------- lib/utils/constants/assets.dart | 3 + 6 files changed, 190 insertions(+), 50 deletions(-) create mode 100644 assets/icons/ac_schedule.svg diff --git a/assets/icons/ac_schedule.svg b/assets/icons/ac_schedule.svg new file mode 100644 index 00000000..eb2394c9 --- /dev/null +++ b/assets/icons/ac_schedule.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart index 94edf837..1930e9c4 100644 --- a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart @@ -10,7 +10,9 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.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/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'; class AcDeviceBatchControlView extends StatelessWidget @@ -75,6 +77,57 @@ class AcDeviceBatchControlView extends StatelessWidget code: 'level', devicesIds: devicesIds, ), + ToggleWidget( + label: '', + labelWidget: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + onPressed: () {}, + icon: const Icon( + Icons.remove, + size: 28, + color: ColorsManager.greyColor, + ), + ), + Text( + '06', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'h', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blackColor), + ), + Text( + '30', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + Text('m', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blackColor)), + IconButton( + onPressed: () {}, + icon: const Icon( + Icons.add, + size: 28, + color: ColorsManager.greyColor, + ), + ), + ], + ), + value: false, + code: 'ac_schedule', + deviceId: devicesIds.first, + icon: Assets.acSchedule, + onChange: (value) {}, + ), ToggleWidget( deviceId: devicesIds.first, code: 'child_lock', diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index c78ce58d..3c383854 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -8,7 +8,9 @@ import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_ import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.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/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'; class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { @@ -74,6 +76,57 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { code: 'level', deviceId: device.uuid!, ), + ToggleWidget( + label: '', + labelWidget: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + onPressed: () {}, + icon: const Icon( + Icons.remove, + size: 28, + color: ColorsManager.greyColor, + ), + ), + Text( + '06', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'h', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blackColor), + ), + Text( + '30', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + Text('m', + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blackColor)), + IconButton( + onPressed: () {}, + icon: const Icon( + Icons.add, + size: 28, + color: ColorsManager.greyColor, + ), + ), + ], + ), + value: false, + code: 'ac_schedule', + deviceId: device.uuid!, + icon: Assets.acSchedule, + onChange: (value) {}, + ), ToggleWidget( deviceId: device.uuid!, code: 'child_lock', diff --git a/lib/pages/device_managment/gateway/view/gateway_view.dart b/lib/pages/device_managment/gateway/view/gateway_view.dart index f6cc4682..ce9b06da 100644 --- a/lib/pages/device_managment/gateway/view/gateway_view.dart +++ b/lib/pages/device_managment/gateway/view/gateway_view.dart @@ -67,22 +67,19 @@ class _DeviceItem extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - width: 60, + ClipOval( + child: Container( height: 60, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(100), - color: ColorsManager.whiteColors, + width: 60, + padding: const EdgeInsets.all(8), + color: ColorsManager.whiteColors, + child: SvgPicture.asset( + device.icon, + width: 35, + height: 35, + fit: BoxFit.contain, ), - margin: const EdgeInsets.symmetric(horizontal: 4), - padding: const EdgeInsets.all(4), - child: ClipOval( - child: SvgPicture.asset( - device.icon, - fit: BoxFit.fill, - ), - ), - ), + )), const Spacer(), Text( device.name ?? 'Unknown Device', diff --git a/lib/pages/device_managment/shared/toggle_widget.dart b/lib/pages/device_managment/shared/toggle_widget.dart index 3115d609..052b04a3 100644 --- a/lib/pages/device_managment/shared/toggle_widget.dart +++ b/lib/pages/device_managment/shared/toggle_widget.dart @@ -11,6 +11,7 @@ class ToggleWidget extends StatelessWidget { final String deviceId; final String label; final String? icon; + final Widget? labelWidget; final Function(dynamic value) onChange; const ToggleWidget({ @@ -21,6 +22,7 @@ class ToggleWidget extends StatelessWidget { required this.label, required this.onChange, this.icon, + this.labelWidget, }); @override @@ -32,32 +34,48 @@ class ToggleWidget extends StatelessWidget { border: Border.all(color: ColorsManager.boxDivider), ), padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - icon == '-1' - ? const SizedBox( - height: 60, - width: 60, - ) - : ClipOval( - child: Container( - height: 60, - width: 60, - padding: const EdgeInsets.all(8), - color: ColorsManager.whiteColors, - child: SvgPicture.asset( - icon ?? Assets.lightPulp, - width: 35, - height: 35, - fit: BoxFit.contain, - ), - )), + Padding( + padding: const EdgeInsets.only(right: 12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + icon == '-1' + ? const SizedBox( + height: 60, + width: 60, + ) + : ClipOval( + child: Container( + height: 60, + width: 60, + padding: const EdgeInsets.all(8), + color: ColorsManager.whiteColors, + child: SvgPicture.asset( + icon ?? Assets.lightPulp, + width: 35, + height: 35, + fit: BoxFit.contain, + ), + )), + Container( + height: 20, + width: 35, + padding: const EdgeInsets.only(right: 16, top: 10), + child: CupertinoSwitch( + value: value, + activeColor: ColorsManager.dialogBlueTitle, + onChanged: onChange, + ), + ), + ], + ), + ), + labelWidget ?? Text( label, style: context.textTheme.titleMedium!.copyWith( @@ -65,18 +83,6 @@ class ToggleWidget extends StatelessWidget { color: ColorsManager.blackColor, ), ), - ], - ), - Container( - height: 20, - width: 35, - padding: const EdgeInsets.only(right: 16, top: 10), - child: CupertinoSwitch( - value: value, - activeColor: ColorsManager.dialogBlueTitle, - onChanged: onChange, - ), - ), ], ), ); diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index ee069c58..3a28a20c 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -169,4 +169,7 @@ class Assets { //assets/icons/ac_lock.svg static const String acLock = 'assets/icons/ac_lock.svg'; + + //assets/icons/ac_schedule.svg + static const String acSchedule = 'assets/icons/ac_schedule.svg'; } From 7661f54427b5e35d2e2ac7ffbd44e379f82a5c39 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Fri, 4 Oct 2024 17:07:00 +0300 Subject: [PATCH 49/65] push 1G , 2G glass switches --- lib/main.dart | 9 +- .../helper/route_controls_based_code.dart | 80 ++++----- .../bloc/one_gang_glass_switch_bloc.dart | 143 +++++++++++++++ .../bloc/one_gang_glass_switch_event.dart | 40 +++++ .../bloc/one_gang_glass_switch_state.dart | 32 ++++ .../models/once_gang_glass_status_model.dart | 50 ++++++ .../one_gang_glass_batch_control_view.dart | 80 +++++++++ .../one_gang_glass_switch_control_view.dart | 71 ++++++++ .../bloc/two_gang_glass_switch_bloc.dart | 164 ++++++++++++++++++ .../bloc/two_gang_glass_switch_event.dart | 50 ++++++ .../bloc/two_gang_glass_switch_state.dart | 32 ++++ .../models/two_gang_glass_status_model.dart | 69 ++++++++ ..._gang_glass_switch_batch_control_view.dart | 103 +++++++++++ .../two_gang_glass_switch_control_view.dart | 86 +++++++++ 14 files changed, 959 insertions(+), 50 deletions(-) create mode 100644 lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart create mode 100644 lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart create mode 100644 lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_state.dart create mode 100644 lib/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart create mode 100644 lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart create mode 100644 lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart create mode 100644 lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart create mode 100644 lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_event.dart create mode 100644 lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_state.dart create mode 100644 lib/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart create mode 100644 lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_batch_control_view.dart create mode 100644 lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart diff --git a/lib/main.dart b/lib/main.dart index 3b861d10..c544f227 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,11 +2,11 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:go_router/go_router.dart'; import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_event.dart'; import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart'; -import 'package:go_router/go_router.dart'; import 'package:syncrow_web/services/locator.dart'; import 'package:syncrow_web/utils/app_routes.dart'; import 'package:syncrow_web/utils/constants/routes_const.dart'; @@ -14,8 +14,7 @@ import 'package:syncrow_web/utils/theme/theme.dart'; Future main() async { try { - const environment = - String.fromEnvironment('FLAVOR', defaultValue: 'development'); + const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development'); await dotenv.load(fileName: '.env.$environment'); WidgetsFlutterBinding.ensureInitialized(); initialSetup(); @@ -45,11 +44,9 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - //HomeBloc.fetchUserInfo(); return MultiBlocProvider( providers: [ - BlocProvider( - create: (context) => HomeBloc()..add(const FetchUserInfo())), + BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())), BlocProvider( create: (context) => VisitorPasswordBloc(), ) diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 068f8c89..03c1f03c 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -13,10 +13,13 @@ import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_co import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart'; +import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_device_control.dart'; +import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_batch_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/view/wall_light_device_control.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart'; @@ -24,6 +27,8 @@ import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_ import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_device_control.dart'; +import '../../one_g_glass_switch/view/one_gang_glass_switch_control_view.dart'; + mixin RouteControlsBasedCode { Widget routeControlsWidgets({required AllDevicesModel device}) { switch (device.productType) { @@ -39,6 +44,18 @@ mixin RouteControlsBasedCode { return LivingRoomDeviceControlsView( deviceId: device.uuid!, ); + case '1GT': + return OneGangGlassSwitchControlView( + deviceId: device.uuid!, + ); + case '2GT': + return TwoGangGlassSwitchControlView( + deviceId: device.uuid!, + ); + // case '3GT': + // return LivingRoomClassSwitchControlView( + // deviceId: device.uuid!, + // ); case 'GW': return GateWayControlsView( gatewayId: device.uuid!, @@ -86,77 +103,52 @@ mixin RouteControlsBasedCode { switch (devices.first.productType) { case '1G': return WallLightBatchControlView( - deviceIds: devices - .where((e) => (e.productType == '1G')) - .map((e) => e.uuid!) - .toList(), + deviceIds: devices.where((e) => (e.productType == '1G')).map((e) => e.uuid!).toList(), ); case '2G': return TwoGangBatchControlView( - deviceIds: devices - .where((e) => (e.productType == '2G')) - .map((e) => e.uuid!) - .toList(), + deviceIds: devices.where((e) => (e.productType == '2G')).map((e) => e.uuid!).toList(), ); case '3G': return LivingRoomBatchControlsView( - deviceIds: devices - .where((e) => (e.productType == '3G')) - .map((e) => e.uuid!) - .toList(), + deviceIds: devices.where((e) => (e.productType == '3G')).map((e) => e.uuid!).toList(), + ); + case '1GT': + return OneGangGlassSwitchBatchControlView( + deviceIds: devices.where((e) => (e.productType == '1GT')).map((e) => e.uuid!).toList(), + ); + case '2GT': + return TwoGangGlassSwitchBatchControlView( + deviceIds: devices.where((e) => (e.productType == '2GT')).map((e) => e.uuid!).toList(), ); case 'GW': return GatewayBatchControlView( - gatewayIds: devices - .where((e) => (e.productType == 'GW')) - .map((e) => e.uuid!) - .toList(), + gatewayIds: devices.where((e) => (e.productType == 'GW')).map((e) => e.uuid!).toList(), ); case 'DL': return DoorLockBatchControlView( - devicesIds: devices - .where((e) => (e.productType == 'DL')) - .map((e) => e.uuid!) - .toList()); + devicesIds: devices.where((e) => (e.productType == 'DL')).map((e) => e.uuid!).toList()); case 'WPS': return WallSensorBatchControlView( - devicesIds: devices - .where((e) => (e.productType == 'WPS')) - .map((e) => e.uuid!) - .toList()); + devicesIds: devices.where((e) => (e.productType == 'WPS')).map((e) => e.uuid!).toList()); case 'CPS': return CeilingSensorBatchControlView( - devicesIds: devices - .where((e) => (e.productType == 'CPS')) - .map((e) => e.uuid!) - .toList(), + devicesIds: devices.where((e) => (e.productType == 'CPS')).map((e) => e.uuid!).toList(), ); case 'CUR': return CurtainBatchStatusView( - devicesIds: devices - .where((e) => (e.productType == 'CUR')) - .map((e) => e.uuid!) - .toList(), + devicesIds: devices.where((e) => (e.productType == 'CUR')).map((e) => e.uuid!).toList(), ); case 'AC': return AcDeviceBatchControlView( - devicesIds: devices - .where((e) => (e.productType == 'AC')) - .map((e) => e.uuid!) - .toList()); + devicesIds: devices.where((e) => (e.productType == 'AC')).map((e) => e.uuid!).toList()); case 'WH': return WaterHEaterBatchControlView( - deviceIds: devices - .where((e) => (e.productType == 'WH')) - .map((e) => e.uuid!) - .toList(), + deviceIds: devices.where((e) => (e.productType == 'WH')).map((e) => e.uuid!).toList(), ); case 'DS': return MainDoorSensorBatchView( - devicesIds: devices - .where((e) => (e.productType == 'DS')) - .map((e) => e.uuid!) - .toList(), + devicesIds: devices.where((e) => (e.productType == 'DS')).map((e) => e.uuid!).toList(), ); default: return const SizedBox(); diff --git a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart new file mode 100644 index 00000000..111d5014 --- /dev/null +++ b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart @@ -0,0 +1,143 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +part 'one_gang_glass_switch_event.dart'; +part 'one_gang_glass_switch_state.dart'; + +class OneGangGlassSwitchBloc extends Bloc { + OneGangGlassStatusModel deviceStatus; + Timer? _timer; + + OneGangGlassSwitchBloc({required String deviceId}) + : deviceStatus = OneGangGlassStatusModel(uuid: deviceId, switch1: false, countDown: 0), + super(OneGangGlassSwitchInitial()) { + on(_onFetchDeviceStatus); + on(_onControl); + on(_onBatchControl); + on(_onFetchBatchStatus); + } + + Future _onFetchDeviceStatus( + OneGangGlassSwitchFetchDeviceEvent event, Emitter emit) async { + emit(OneGangGlassSwitchLoading()); + try { + final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status); + emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); + } catch (e) { + emit(OneGangGlassSwitchError(e.toString())); + } + } + + Future _onControl(OneGangGlassSwitchControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: false, + ); + } + + Future _onBatchControl(OneGangGlassSwitchBatchControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + emit(OneGangGlassSwitchBatchStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceIds, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: true, + ); + } + + Future _onFetchBatchStatus( + OneGangGlassSwitchFetchBatchStatusEvent event, Emitter emit) async { + emit(OneGangGlassSwitchLoading()); + try { + final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceIds.first, status.status); + emit(OneGangGlassSwitchBatchStatusLoaded(deviceStatus)); + } catch (e) { + emit(OneGangGlassSwitchError(e.toString())); + } + } + + Future _runDebounce({ + required dynamic deviceId, + required String code, + required bool value, + required bool oldValue, + required Emitter emit, + required bool isBatch, + }) async { + late String id; + if (deviceId is List) { + id = deviceId.first; + } else { + id = deviceId; + } + + if (_timer != null) { + _timer!.cancel(); + } + + _timer = Timer(const Duration(milliseconds: 500), () async { + try { + late bool response; + if (isBatch) { + response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + } else { + response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); + } + + if (!response) { + _revertValueAndEmit(id, code, oldValue, emit); + } + } catch (e) { + _revertValueAndEmit(id, code, oldValue, emit); + } + }); + } + + void _revertValueAndEmit(String deviceId, String code, bool oldValue, Emitter emit) { + _updateLocalValue(code, oldValue); + emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); + } + + void _updateLocalValue(String code, bool value) { + if (code == 'switch_1') { + deviceStatus = deviceStatus.copyWith(switch1: value); + } + } + + bool _getValueByCode(String code) { + switch (code) { + case 'switch_1': + return deviceStatus.switch1; + default: + return false; + } + } + + @override + Future close() { + _timer?.cancel(); + return super.close(); + } +} diff --git a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart new file mode 100644 index 00000000..993e5a8c --- /dev/null +++ b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart @@ -0,0 +1,40 @@ +part of 'one_gang_glass_switch_bloc.dart'; + +@immutable +abstract class OneGangGlassSwitchEvent {} + +class OneGangGlassSwitchFetchDeviceEvent extends OneGangGlassSwitchEvent { + final String deviceId; + + OneGangGlassSwitchFetchDeviceEvent(this.deviceId); +} + +class OneGangGlassSwitchControl extends OneGangGlassSwitchEvent { + final String deviceId; + final String code; + final bool value; + + OneGangGlassSwitchControl({ + required this.deviceId, + required this.code, + required this.value, + }); +} + +class OneGangGlassSwitchBatchControl extends OneGangGlassSwitchEvent { + final List deviceIds; + final String code; + final bool value; + + OneGangGlassSwitchBatchControl({ + required this.deviceIds, + required this.code, + required this.value, + }); +} + +class OneGangGlassSwitchFetchBatchStatusEvent extends OneGangGlassSwitchEvent { + final List deviceIds; + + OneGangGlassSwitchFetchBatchStatusEvent(this.deviceIds); +} diff --git a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_state.dart b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_state.dart new file mode 100644 index 00000000..6059d5eb --- /dev/null +++ b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_state.dart @@ -0,0 +1,32 @@ +part of 'one_gang_glass_switch_bloc.dart'; + +@immutable +abstract class OneGangGlassSwitchState {} + +class OneGangGlassSwitchInitial extends OneGangGlassSwitchState {} + +class OneGangGlassSwitchLoading extends OneGangGlassSwitchState {} + +class OneGangGlassSwitchStatusLoaded extends OneGangGlassSwitchState { + final OneGangGlassStatusModel status; + + OneGangGlassSwitchStatusLoaded(this.status); +} + +class OneGangGlassSwitchError extends OneGangGlassSwitchState { + final String message; + + OneGangGlassSwitchError(this.message); +} + +class OneGangGlassSwitchBatchStatusLoaded extends OneGangGlassSwitchState { + final OneGangGlassStatusModel status; + + OneGangGlassSwitchBatchStatusLoaded(this.status); +} + +class OneGangGlassSwitchBatchControlError extends OneGangGlassSwitchState { + final String message; + + OneGangGlassSwitchBatchControlError(this.message); +} diff --git a/lib/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart b/lib/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart new file mode 100644 index 00000000..39c96dd0 --- /dev/null +++ b/lib/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart @@ -0,0 +1,50 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class OneGangGlassStatusModel { + final String uuid; + final bool switch1; + final int countDown; + + OneGangGlassStatusModel({ + required this.uuid, + required this.switch1, + required this.countDown, + }); + + factory OneGangGlassStatusModel.fromJson(String id, List jsonList) { + late bool switch1; + late int countDown; + + for (var status in jsonList) { + switch (status.code) { + case 'switch_1': + switch1 = status.value ?? false; + break; + case 'countdown_1': + countDown = status.value ?? 0; + break; + } + } + + return OneGangGlassStatusModel( + uuid: id, + switch1: switch1, + countDown: countDown, + ); + } + + OneGangGlassStatusModel copyWith({ + String? uuid, + bool? switch1, + int? countDown, + }) { + return OneGangGlassStatusModel( + uuid: uuid ?? this.uuid, + switch1: switch1 ?? this.switch1, + countDown: countDown ?? this.countDown, + ); + } + + @override + String toString() => 'OneGangGlassStatusModel(uuid: $uuid, switch1: $switch1, countDown: $countDown)'; +} diff --git a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart new file mode 100644 index 00000000..e81f448c --- /dev/null +++ b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResponsiveLayout { + final List deviceIds; + + const OneGangGlassSwitchBatchControlView({required this.deviceIds, super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => + OneGangGlassSwitchBloc(deviceId: deviceIds.first)..add(OneGangGlassSwitchFetchBatchStatusEvent(deviceIds)), + child: BlocBuilder( + builder: (context, state) { + if (state is OneGangGlassSwitchLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is OneGangGlassSwitchStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is OneGangGlassSwitchError) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls(BuildContext context, OneGangGlassStatusModel 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, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceIds.first, + label: 'Wall Light', + onChange: (value) { + context.read().add( + OneGangGlassSwitchBatchControl( + deviceIds: deviceIds, + code: 'switch_1', + value: value, + ), + ); + }, + ), + FirmwareUpdateWidget( + deviceId: deviceIds.first, + version: 12, // adjust the version according to your requirement + ), + FactoryResetWidget( + callFactoryReset: () {}, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart new file mode 100644 index 00000000..8403185f --- /dev/null +++ b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout { + final String deviceId; + + const OneGangGlassSwitchControlView({required this.deviceId, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => + OneGangGlassSwitchBloc(deviceId: deviceId)..add(OneGangGlassSwitchFetchDeviceEvent(deviceId)), + child: BlocBuilder( + builder: (context, state) { + if (state is OneGangGlassSwitchLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is OneGangGlassSwitchStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is OneGangGlassSwitchError) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls(BuildContext context, OneGangGlassStatusModel 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, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceId, + label: 'Wall Lightً', + onChange: (value) { + context.read().add( + OneGangGlassSwitchControl( + deviceId: deviceId, + code: 'switch_1', + value: value, + ), + ); + }, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart new file mode 100644 index 00000000..be2ab687 --- /dev/null +++ b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart @@ -0,0 +1,164 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +part 'two_gang_glass_switch_event.dart'; +part 'two_gang_glass_switch_state.dart'; + +class TwoGangGlassSwitchBloc extends Bloc { + TwoGangGlassStatusModel deviceStatus; + Timer? _timer; + + TwoGangGlassSwitchBloc({required String deviceId}) + : deviceStatus = + TwoGangGlassStatusModel(uuid: deviceId, switch1: false, countDown1: 0, switch2: false, countDown2: 0), + super(TwoGangGlassSwitchInitial()) { + on(_onFetchDeviceStatus); + on(_onControl); + on(_onBatchControl); + on(_onFetchBatchStatus); + on(_onFactoryReset); + } + + Future _onFetchDeviceStatus( + TwoGangGlassSwitchFetchDeviceEvent event, Emitter emit) async { + emit(TwoGangGlassSwitchLoading()); + try { + final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceId, status.status); + emit(TwoGangGlassSwitchStatusLoaded(deviceStatus)); + } catch (e) { + emit(TwoGangGlassSwitchError(e.toString())); + } + } + + Future _onControl(TwoGangGlassSwitchControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + emit(TwoGangGlassSwitchStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: false, + ); + } + + Future _onBatchControl(TwoGangGlassSwitchBatchControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceIds, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: true, + ); + } + + Future _onFetchBatchStatus( + TwoGangGlassSwitchFetchBatchStatusEvent event, Emitter emit) async { + emit(TwoGangGlassSwitchLoading()); + try { + final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceIds.first, status.status); + emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus)); + } catch (e) { + emit(TwoGangGlassSwitchError(e.toString())); + } + } + + Future _onFactoryReset(TwoGangGlassFactoryReset event, Emitter emit) async { + emit(TwoGangGlassSwitchLoading()); + try { + final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId); + if (!response) { + emit(TwoGangGlassSwitchError('Failed')); + } else { + emit(TwoGangGlassSwitchStatusLoaded(deviceStatus)); + } + } catch (e) { + emit(TwoGangGlassSwitchError(e.toString())); + } + } + + Future _runDebounce({ + required dynamic deviceId, + required String code, + required bool value, + required bool oldValue, + required Emitter emit, + required bool isBatch, + }) async { + late String id; + if (deviceId is List) { + id = deviceId.first; + } else { + id = deviceId; + } + + if (_timer != null) { + _timer!.cancel(); + } + + _timer = Timer(const Duration(milliseconds: 500), () async { + try { + late bool response; + if (isBatch) { + response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + } else { + response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); + } + + if (!response) { + _revertValueAndEmit(id, code, oldValue, emit); + } + } catch (e) { + _revertValueAndEmit(id, code, oldValue, emit); + } + }); + } + + void _revertValueAndEmit(String deviceId, String code, bool oldValue, Emitter emit) { + _updateLocalValue(code, oldValue); + emit(TwoGangGlassSwitchStatusLoaded(deviceStatus)); + } + + void _updateLocalValue(String code, bool value) { + if (code == 'switch_1') { + deviceStatus = deviceStatus.copyWith(switch1: value); + } else if (code == 'switch_2') { + deviceStatus = deviceStatus.copyWith(switch2: value); + } + } + + bool _getValueByCode(String code) { + switch (code) { + case 'switch_1': + return deviceStatus.switch1; + case 'switch_2': + return deviceStatus.switch2; + default: + return false; + } + } + + @override + Future close() { + _timer?.cancel(); + return super.close(); + } +} diff --git a/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_event.dart b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_event.dart new file mode 100644 index 00000000..f88d61fe --- /dev/null +++ b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_event.dart @@ -0,0 +1,50 @@ +part of 'two_gang_glass_switch_bloc.dart'; + +@immutable +abstract class TwoGangGlassSwitchEvent {} + +class TwoGangGlassSwitchFetchDeviceEvent extends TwoGangGlassSwitchEvent { + final String deviceId; + + TwoGangGlassSwitchFetchDeviceEvent(this.deviceId); +} + +class TwoGangGlassSwitchControl extends TwoGangGlassSwitchEvent { + final String deviceId; + final String code; + final bool value; + + TwoGangGlassSwitchControl({ + required this.deviceId, + required this.code, + required this.value, + }); +} + +class TwoGangGlassSwitchBatchControl extends TwoGangGlassSwitchEvent { + final List deviceIds; + final String code; + final bool value; + + TwoGangGlassSwitchBatchControl({ + required this.deviceIds, + required this.code, + required this.value, + }); +} + +class TwoGangGlassSwitchFetchBatchStatusEvent extends TwoGangGlassSwitchEvent { + final List deviceIds; + + TwoGangGlassSwitchFetchBatchStatusEvent(this.deviceIds); +} + +class TwoGangGlassFactoryReset extends TwoGangGlassSwitchEvent { + final String deviceId; + final FactoryResetModel factoryReset; + + TwoGangGlassFactoryReset({ + required this.deviceId, + required this.factoryReset, + }); +} diff --git a/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_state.dart b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_state.dart new file mode 100644 index 00000000..3f95a514 --- /dev/null +++ b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_state.dart @@ -0,0 +1,32 @@ +part of 'two_gang_glass_switch_bloc.dart'; + +@immutable +abstract class TwoGangGlassSwitchState {} + +class TwoGangGlassSwitchInitial extends TwoGangGlassSwitchState {} + +class TwoGangGlassSwitchLoading extends TwoGangGlassSwitchState {} + +class TwoGangGlassSwitchStatusLoaded extends TwoGangGlassSwitchState { + final TwoGangGlassStatusModel status; + + TwoGangGlassSwitchStatusLoaded(this.status); +} + +class TwoGangGlassSwitchError extends TwoGangGlassSwitchState { + final String message; + + TwoGangGlassSwitchError(this.message); +} + +class TwoGangGlassSwitchBatchStatusLoaded extends TwoGangGlassSwitchState { + final TwoGangGlassStatusModel status; + + TwoGangGlassSwitchBatchStatusLoaded(this.status); +} + +class TwoGangGlassSwitchBatchControlError extends TwoGangGlassSwitchState { + final String message; + + TwoGangGlassSwitchBatchControlError(this.message); +} diff --git a/lib/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart b/lib/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart new file mode 100644 index 00000000..54d99d74 --- /dev/null +++ b/lib/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart @@ -0,0 +1,69 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class TwoGangGlassStatusModel { + final String uuid; + final bool switch1; + final int countDown1; + final bool switch2; + final int countDown2; + + TwoGangGlassStatusModel({ + required this.uuid, + required this.switch1, + required this.countDown1, + required this.switch2, + required this.countDown2, + }); + + factory TwoGangGlassStatusModel.fromJson(String id, List jsonList) { + late bool switch1; + late int countDown1; + late bool switch2; + late int countDown2; + + for (var status in jsonList) { + switch (status.code) { + case 'switch_1': + switch1 = status.value ?? false; + break; + case 'countdown_1': + countDown1 = status.value ?? 0; + break; + case 'switch_2': + switch2 = status.value ?? false; + break; + case 'countdown_2': + countDown2 = status.value ?? 0; + break; + } + } + + return TwoGangGlassStatusModel( + uuid: id, + switch1: switch1, + countDown1: countDown1, + switch2: switch2, + countDown2: countDown2, + ); + } + + TwoGangGlassStatusModel copyWith({ + String? uuid, + bool? switch1, + int? countDown1, + bool? switch2, + int? countDown2, + }) { + return TwoGangGlassStatusModel( + uuid: uuid ?? this.uuid, + switch1: switch1 ?? this.switch1, + countDown1: countDown1 ?? this.countDown1, + switch2: switch2 ?? this.switch2, + countDown2: countDown2 ?? this.countDown2, + ); + } + + @override + String toString() => + 'TwoGangGlassStatusModel(uuid: $uuid, switch1: $switch1, countDown1: $countDown1, switch2: $switch2, countDown2: $countDown2)'; +} diff --git a/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_batch_control_view.dart b/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_batch_control_view.dart new file mode 100644 index 00000000..d0288ca3 --- /dev/null +++ b/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_batch_control_view.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; +import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class TwoGangGlassSwitchBatchControlView extends StatelessWidget with HelperResponsiveLayout { + final List deviceIds; + + const TwoGangGlassSwitchBatchControlView({required this.deviceIds, super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => + TwoGangGlassSwitchBloc(deviceId: deviceIds.first)..add(TwoGangGlassSwitchFetchBatchStatusEvent(deviceIds)), + child: BlocBuilder( + builder: (context, state) { + if (state is TwoGangGlassSwitchLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is TwoGangGlassSwitchBatchStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is TwoGangGlassSwitchError) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls(BuildContext context, TwoGangGlassStatusModel 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, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceIds.first, + label: 'Wall Light', + onChange: (value) { + context.read().add( + TwoGangGlassSwitchBatchControl( + deviceIds: deviceIds, + code: 'switch_1', + value: value, + ), + ); + }, + ), + ToggleWidget( + value: status.switch2, + code: 'switch_2', + deviceId: deviceIds.first, + label: 'Ceiling Light', + onChange: (value) { + context.read().add( + TwoGangGlassSwitchBatchControl( + deviceIds: deviceIds, + code: 'switch_2', + value: value, + ), + ); + }, + ), + FirmwareUpdateWidget( + deviceId: deviceIds.first, + version: 12, // adjust the version according to your requirement + ), + FactoryResetWidget( + callFactoryReset: () { + context.read().add( + TwoGangGlassFactoryReset( + deviceId: status.uuid, + factoryReset: FactoryResetModel(devicesUuid: deviceIds), + ), + ); + }, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart b/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart new file mode 100644 index 00000000..8d1b9c75 --- /dev/null +++ b/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; +import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class TwoGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout { + final String deviceId; + + const TwoGangGlassSwitchControlView({required this.deviceId, super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => + TwoGangGlassSwitchBloc(deviceId: deviceId)..add(TwoGangGlassSwitchFetchDeviceEvent(deviceId)), + child: BlocBuilder( + builder: (context, state) { + if (state is TwoGangGlassSwitchLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is TwoGangGlassSwitchStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is TwoGangGlassSwitchError) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls(BuildContext context, TwoGangGlassStatusModel 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, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceId, + label: 'Wall Light', + onChange: (value) { + context.read().add( + TwoGangGlassSwitchControl( + deviceId: deviceId, + code: 'switch_1', + value: value, + ), + ); + }, + ), + ToggleWidget( + value: status.switch2, + code: 'switch_2', + deviceId: deviceId, + label: 'Ceiling Light', + onChange: (value) { + context.read().add( + TwoGangGlassSwitchControl( + deviceId: deviceId, + code: 'switch_2', + value: value, + ), + ); + }, + ), + ], + ); + } +} From 47f1a9a6cda9fe4ee1628fecaa4e252acf07939a Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Fri, 4 Oct 2024 17:40:52 +0300 Subject: [PATCH 50/65] push 3G glass switches --- assets/icons/preferences.svg | 20 ++ .../helper/route_controls_based_code.dart | 14 +- .../one_gang_glass_switch_control_view.dart | 19 ++ .../shared/toggle_widget.dart | 22 ++- .../bloc/three_gang_glass_switch_bloc.dart | 174 ++++++++++++++++++ .../bloc/three_gang_glass_switch_event.dart | 50 +++++ .../bloc/three_gang_glass_switch_state.dart | 32 ++++ .../models/three_gang_glass_switch.dart | 87 +++++++++ ..._gang_glass_switch_batch_control_view.dart | 118 ++++++++++++ .../three_gang_glass_switch_control_view.dart | 121 ++++++++++++ .../two_gang_glass_switch_control_view.dart | 19 ++ lib/utils/constants/assets.dart | 78 +++----- 12 files changed, 690 insertions(+), 64 deletions(-) create mode 100644 assets/icons/preferences.svg create mode 100644 lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart create mode 100644 lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart create mode 100644 lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_state.dart create mode 100644 lib/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart create mode 100644 lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart create mode 100644 lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart diff --git a/assets/icons/preferences.svg b/assets/icons/preferences.svg new file mode 100644 index 00000000..0a7a7fc0 --- /dev/null +++ b/assets/icons/preferences.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 03c1f03c..eca980c6 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -16,6 +16,8 @@ import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_do import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart'; +import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_batch_controls.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/view/living_room_device_control.dart'; import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_batch_control_view.dart'; @@ -52,10 +54,10 @@ mixin RouteControlsBasedCode { return TwoGangGlassSwitchControlView( deviceId: device.uuid!, ); - // case '3GT': - // return LivingRoomClassSwitchControlView( - // deviceId: device.uuid!, - // ); + case '3GT': + return ThreeGangGlassSwitchControlView( + deviceId: device.uuid!, + ); case 'GW': return GateWayControlsView( gatewayId: device.uuid!, @@ -121,6 +123,10 @@ mixin RouteControlsBasedCode { return TwoGangGlassSwitchBatchControlView( deviceIds: devices.where((e) => (e.productType == '2GT')).map((e) => e.uuid!).toList(), ); + case '3GT': + return ThreeGangGlassSwitchBatchControlView( + deviceIds: devices.where((e) => (e.productType == '3GT')).map((e) => e.uuid!).toList(), + ); case 'GW': return GatewayBatchControlView( gatewayIds: devices.where((e) => (e.productType == 'GW')).map((e) => e.uuid!).toList(), diff --git a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart index 8403185f..77b3dc9e 100644 --- a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart +++ b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout { @@ -65,6 +66,24 @@ class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiv ); }, ), + ToggleWidget( + value: false, + code: '', + deviceId: deviceId, + label: 'Preferences', + icon: Assets.preferences, + onChange: (value) {}, + showToggle: false, + ), + ToggleWidget( + value: false, + code: '', + deviceId: deviceId, + label: 'Scheduling', + icon: Assets.scheduling, + onChange: (value) {}, + showToggle: false, + ), ], ); } diff --git a/lib/pages/device_managment/shared/toggle_widget.dart b/lib/pages/device_managment/shared/toggle_widget.dart index 052b04a3..ee76d442 100644 --- a/lib/pages/device_managment/shared/toggle_widget.dart +++ b/lib/pages/device_managment/shared/toggle_widget.dart @@ -1,5 +1,4 @@ import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -13,6 +12,7 @@ class ToggleWidget extends StatelessWidget { final String? icon; final Widget? labelWidget; final Function(dynamic value) onChange; + final bool showToggle; const ToggleWidget({ super.key, @@ -23,6 +23,7 @@ class ToggleWidget extends StatelessWidget { required this.onChange, this.icon, this.labelWidget, + this.showToggle = true, }); @override @@ -62,16 +63,17 @@ class ToggleWidget extends StatelessWidget { fit: BoxFit.contain, ), )), - Container( - height: 20, - width: 35, - padding: const EdgeInsets.only(right: 16, top: 10), - child: CupertinoSwitch( - value: value, - activeColor: ColorsManager.dialogBlueTitle, - onChanged: onChange, + if (showToggle) + Container( + height: 20, + width: 35, + padding: const EdgeInsets.only(right: 16, top: 10), + child: CupertinoSwitch( + value: value, + activeColor: ColorsManager.dialogBlueTitle, + onChanged: onChange, + ), ), - ), ], ), ), diff --git a/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart new file mode 100644 index 00000000..8e3c109e --- /dev/null +++ b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart @@ -0,0 +1,174 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +part 'three_gang_glass_switch_event.dart'; +part 'three_gang_glass_switch_state.dart'; + +class ThreeGangGlassSwitchBloc extends Bloc { + ThreeGangGlassStatusModel deviceStatus; + Timer? _timer; + + ThreeGangGlassSwitchBloc({required String deviceId}) + : deviceStatus = ThreeGangGlassStatusModel( + uuid: deviceId, + switch1: false, + countDown1: 0, + switch2: false, + countDown2: 0, + switch3: false, + countDown3: 0), + super(ThreeGangGlassSwitchInitial()) { + on(_onFetchDeviceStatus); + on(_onControl); + on(_onBatchControl); + on(_onFetchBatchStatus); + on(_onFactoryReset); + } + + Future _onFetchDeviceStatus( + ThreeGangGlassSwitchFetchDeviceEvent event, Emitter emit) async { + emit(ThreeGangGlassSwitchLoading()); + try { + final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status); + emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus)); + } catch (e) { + emit(ThreeGangGlassSwitchError(e.toString())); + } + } + + Future _onControl(ThreeGangGlassSwitchControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: false, + ); + } + + Future _onBatchControl(ThreeGangGlassSwitchBatchControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceIds, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: true, + ); + } + + Future _onFetchBatchStatus( + ThreeGangGlassSwitchFetchBatchStatusEvent event, Emitter emit) async { + emit(ThreeGangGlassSwitchLoading()); + try { + final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = ThreeGangGlassStatusModel.fromJson(event.deviceIds.first, status.status); + emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus)); + } catch (e) { + emit(ThreeGangGlassSwitchError(e.toString())); + } + } + + Future _onFactoryReset(ThreeGangGlassFactoryReset event, Emitter emit) async { + emit(ThreeGangGlassSwitchLoading()); + try { + final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId); + if (!response) { + emit(ThreeGangGlassSwitchError('Failed')); + } else { + emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus)); + } + } catch (e) { + emit(ThreeGangGlassSwitchError(e.toString())); + } + } + + Future _runDebounce({ + required dynamic deviceId, + required String code, + required bool value, + required bool oldValue, + required Emitter emit, + required bool isBatch, + }) async { + late String id; + if (deviceId is List) { + id = deviceId.first; + } else { + id = deviceId; + } + + if (_timer != null) { + _timer!.cancel(); + } + + _timer = Timer(const Duration(milliseconds: 500), () async { + try { + late bool response; + if (isBatch) { + response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + } else { + response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); + } + + if (!response) { + _revertValueAndEmit(id, code, oldValue, emit); + } + } catch (e) { + _revertValueAndEmit(id, code, oldValue, emit); + } + }); + } + + void _revertValueAndEmit(String deviceId, String code, bool oldValue, Emitter emit) { + _updateLocalValue(code, oldValue); + emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus)); + } + + void _updateLocalValue(String code, bool value) { + if (code == 'switch_1') { + deviceStatus = deviceStatus.copyWith(switch1: value); + } else if (code == 'switch_2') { + deviceStatus = deviceStatus.copyWith(switch2: value); + } else if (code == 'switch_3') { + deviceStatus = deviceStatus.copyWith(switch3: value); + } + } + + bool _getValueByCode(String code) { + switch (code) { + case 'switch_1': + return deviceStatus.switch1; + case 'switch_2': + return deviceStatus.switch2; + case 'switch_3': + return deviceStatus.switch3; + default: + return false; + } + } + + @override + Future close() { + _timer?.cancel(); + return super.close(); + } +} diff --git a/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart new file mode 100644 index 00000000..a40888e7 --- /dev/null +++ b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart @@ -0,0 +1,50 @@ +part of 'three_gang_glass_switch_bloc.dart'; + +@immutable +abstract class ThreeGangGlassSwitchEvent {} + +class ThreeGangGlassSwitchFetchDeviceEvent extends ThreeGangGlassSwitchEvent { + final String deviceId; + + ThreeGangGlassSwitchFetchDeviceEvent(this.deviceId); +} + +class ThreeGangGlassSwitchControl extends ThreeGangGlassSwitchEvent { + final String deviceId; + final String code; + final bool value; + + ThreeGangGlassSwitchControl({ + required this.deviceId, + required this.code, + required this.value, + }); +} + +class ThreeGangGlassSwitchBatchControl extends ThreeGangGlassSwitchEvent { + final List deviceIds; + final String code; + final bool value; + + ThreeGangGlassSwitchBatchControl({ + required this.deviceIds, + required this.code, + required this.value, + }); +} + +class ThreeGangGlassSwitchFetchBatchStatusEvent extends ThreeGangGlassSwitchEvent { + final List deviceIds; + + ThreeGangGlassSwitchFetchBatchStatusEvent(this.deviceIds); +} + +class ThreeGangGlassFactoryReset extends ThreeGangGlassSwitchEvent { + final String deviceId; + final FactoryResetModel factoryReset; + + ThreeGangGlassFactoryReset({ + required this.deviceId, + required this.factoryReset, + }); +} diff --git a/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_state.dart b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_state.dart new file mode 100644 index 00000000..aedb3a9b --- /dev/null +++ b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_state.dart @@ -0,0 +1,32 @@ +part of 'three_gang_glass_switch_bloc.dart'; + +@immutable +abstract class ThreeGangGlassSwitchState {} + +class ThreeGangGlassSwitchInitial extends ThreeGangGlassSwitchState {} + +class ThreeGangGlassSwitchLoading extends ThreeGangGlassSwitchState {} + +class ThreeGangGlassSwitchStatusLoaded extends ThreeGangGlassSwitchState { + final ThreeGangGlassStatusModel status; + + ThreeGangGlassSwitchStatusLoaded(this.status); +} + +class ThreeGangGlassSwitchError extends ThreeGangGlassSwitchState { + final String message; + + ThreeGangGlassSwitchError(this.message); +} + +class ThreeGangGlassSwitchBatchStatusLoaded extends ThreeGangGlassSwitchState { + final ThreeGangGlassStatusModel status; + + ThreeGangGlassSwitchBatchStatusLoaded(this.status); +} + +class ThreeGangGlassSwitchBatchControlError extends ThreeGangGlassSwitchState { + final String message; + + ThreeGangGlassSwitchBatchControlError(this.message); +} diff --git a/lib/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart b/lib/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart new file mode 100644 index 00000000..cec12b7f --- /dev/null +++ b/lib/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart @@ -0,0 +1,87 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class ThreeGangGlassStatusModel { + final String uuid; + final bool switch1; + final int countDown1; + final bool switch2; + final int countDown2; + final bool switch3; + final int countDown3; + + ThreeGangGlassStatusModel({ + required this.uuid, + required this.switch1, + required this.countDown1, + required this.switch2, + required this.countDown2, + required this.switch3, + required this.countDown3, + }); + + factory ThreeGangGlassStatusModel.fromJson(String id, List jsonList) { + late bool switch1; + late int countDown1; + late bool switch2; + late int countDown2; + late bool switch3; + late int countDown3; + + for (var status in jsonList) { + switch (status.code) { + case 'switch_1': + switch1 = status.value ?? false; + break; + case 'countdown_1': + countDown1 = status.value ?? 0; + break; + case 'switch_2': + switch2 = status.value ?? false; + break; + case 'countdown_2': + countDown2 = status.value ?? 0; + break; + case 'switch_3': + switch3 = status.value ?? false; + break; + case 'countdown_3': + countDown3 = status.value ?? 0; + break; + } + } + + return ThreeGangGlassStatusModel( + uuid: id, + switch1: switch1, + countDown1: countDown1, + switch2: switch2, + countDown2: countDown2, + switch3: switch3, + countDown3: countDown3, + ); + } + + ThreeGangGlassStatusModel copyWith({ + String? uuid, + bool? switch1, + int? countDown1, + bool? switch2, + int? countDown2, + bool? switch3, + int? countDown3, + }) { + return ThreeGangGlassStatusModel( + uuid: uuid ?? this.uuid, + switch1: switch1 ?? this.switch1, + countDown1: countDown1 ?? this.countDown1, + switch2: switch2 ?? this.switch2, + countDown2: countDown2 ?? this.countDown2, + switch3: switch3 ?? this.switch3, + countDown3: countDown3 ?? this.countDown3, + ); + } + + @override + String toString() => + 'ThreeGangGlassStatusModel(uuid: $uuid, switch1: $switch1, countDown1: $countDown1, switch2: $switch2, countDown2: $countDown2, switch3: $switch3, countDown3: $countDown3)'; +} diff --git a/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart b/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart new file mode 100644 index 00000000..f7118ae6 --- /dev/null +++ b/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; +import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class ThreeGangGlassSwitchBatchControlView extends StatelessWidget with HelperResponsiveLayout { + final List deviceIds; + + const ThreeGangGlassSwitchBatchControlView({required this.deviceIds, super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => ThreeGangGlassSwitchBloc(deviceId: deviceIds.first) + ..add(ThreeGangGlassSwitchFetchBatchStatusEvent(deviceIds)), + child: BlocBuilder( + builder: (context, state) { + if (state is ThreeGangGlassSwitchLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is ThreeGangGlassSwitchBatchStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is ThreeGangGlassSwitchError) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls(BuildContext context, ThreeGangGlassStatusModel 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, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceIds.first, + label: 'Glass Switch 1', + onChange: (value) { + context.read().add( + ThreeGangGlassSwitchBatchControl( + deviceIds: deviceIds, + code: 'switch_1', + value: value, + ), + ); + }, + ), + ToggleWidget( + value: status.switch2, + code: 'switch_2', + deviceId: deviceIds.first, + label: 'Glass Switch 2', + onChange: (value) { + context.read().add( + ThreeGangGlassSwitchBatchControl( + deviceIds: deviceIds, + code: 'switch_2', + value: value, + ), + ); + }, + ), + ToggleWidget( + value: status.switch3, + code: 'switch_3', + deviceId: deviceIds.first, + label: 'Glass Switch 3', + onChange: (value) { + context.read().add( + ThreeGangGlassSwitchBatchControl( + deviceIds: deviceIds, + code: 'switch_3', + value: value, + ), + ); + }, + ), + FirmwareUpdateWidget( + deviceId: deviceIds.first, + version: 12, // adjust the version according to your requirement + ), + FactoryResetWidget( + callFactoryReset: () { + context.read().add( + ThreeGangGlassFactoryReset( + deviceId: status.uuid, + factoryReset: FactoryResetModel(devicesUuid: deviceIds), + ), + ); + }, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart b/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart new file mode 100644 index 00000000..2f14eaac --- /dev/null +++ b/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; +import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +import '../models/three_gang_glass_switch.dart'; + +class ThreeGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout { + final String deviceId; + + const ThreeGangGlassSwitchControlView({required this.deviceId, super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => + ThreeGangGlassSwitchBloc(deviceId: deviceId)..add(ThreeGangGlassSwitchFetchDeviceEvent(deviceId)), + child: BlocBuilder( + builder: (context, state) { + if (state is ThreeGangGlassSwitchLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is ThreeGangGlassSwitchStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is ThreeGangGlassSwitchError) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls(BuildContext context, ThreeGangGlassStatusModel 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, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceId, + label: 'Wall Light', + onChange: (value) { + context.read().add( + ThreeGangGlassSwitchControl( + deviceId: deviceId, + code: 'switch_1', + value: value, + ), + ); + }, + ), + ToggleWidget( + value: status.switch2, + code: 'switch_2', + deviceId: deviceId, + label: 'Ceiling Light', + onChange: (value) { + context.read().add( + ThreeGangGlassSwitchControl( + deviceId: deviceId, + code: 'switch_2', + value: value, + ), + ); + }, + ), + ToggleWidget( + value: status.switch3, + code: 'switch_3', + deviceId: deviceId, + label: 'SpotLight', + onChange: (value) { + context.read().add( + ThreeGangGlassSwitchControl( + deviceId: deviceId, + code: 'switch_3', + value: value, + ), + ); + }, + ), + ToggleWidget( + value: false, + code: '', + deviceId: deviceId, + label: 'Preferences', + icon: Assets.preferences, + onChange: (value) {}, + showToggle: false, + ), + ToggleWidget( + value: false, + code: '', + deviceId: deviceId, + label: 'Scheduling', + icon: Assets.scheduling, + onChange: (value) {}, + showToggle: false, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart b/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart index 8d1b9c75..72f69763 100644 --- a/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart +++ b/lib/pages/device_managment/two_g_glass_switch/view/two_gang_glass_switch_control_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; class TwoGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout { @@ -80,6 +81,24 @@ class TwoGangGlassSwitchControlView extends StatelessWidget with HelperResponsiv ); }, ), + ToggleWidget( + value: false, + code: '', + deviceId: deviceId, + label: 'Preferences', + icon: Assets.preferences, + onChange: (value) {}, + showToggle: false, + ), + ToggleWidget( + value: false, + code: '', + deviceId: deviceId, + label: 'Scheduling', + icon: Assets.scheduling, + onChange: (value) {}, + showToggle: false, + ), ], ); } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 3a28a20c..ff1b3c15 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -13,12 +13,10 @@ class Assets { static const String rightLine = "assets/images/right_line.png"; static const String google = "assets/images/google.svg"; static const String facebook = "assets/images/facebook.svg"; - static const String invisiblePassword = - "assets/images/Password_invisible.svg"; + static const String invisiblePassword = "assets/images/Password_invisible.svg"; static const String visiblePassword = "assets/images/password_visible.svg"; static const String accessIcon = "assets/images/access_icon.svg"; - static const String spaseManagementIcon = - "assets/images/spase_management_icon.svg"; + static const String spaseManagementIcon = "assets/images/spase_management_icon.svg"; static const String devicesIcon = "assets/images/devices_icon.svg"; static const String moveinIcon = "assets/images/movein_icon.svg"; static const String constructionIcon = "assets/images/construction_icon.svg"; @@ -31,15 +29,13 @@ class Assets { static const String emptyTable = "assets/images/empty_table.svg"; // General assets - static const String motionlessDetection = - "assets/icons/motionless_detection.svg"; + static const String motionlessDetection = "assets/icons/motionless_detection.svg"; static const String acHeating = "assets/icons/ac_heating.svg"; static const String acPowerOff = "assets/icons/ac_power_off.svg"; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg"; static const String resetOff = "assets/icons/reset_off.svg"; - static const String sensitivityOperationIcon = - "assets/icons/sesitivity_operation_icon.svg"; + static const String sensitivityOperationIcon = "assets/icons/sesitivity_operation_icon.svg"; static const String motionDetection = "assets/icons/motion_detection.svg"; static const String freezing = "assets/icons/freezing.svg"; static const String indicator = "assets/icons/indicator.svg"; @@ -60,56 +56,35 @@ class Assets { static const String celsiusDegrees = "assets/icons/celsius_degrees.svg"; static const String masterState = "assets/icons/master_state.svg"; static const String acPower = "assets/icons/ac_power.svg"; - static const String farDetectionFunction = - "assets/icons/far_detection_function.svg"; + static const String farDetectionFunction = "assets/icons/far_detection_function.svg"; static const String nobodyTime = "assets/icons/nobody_time.svg"; // Automation functions - static const String tempPasswordUnlock = - "assets/icons/automation_functions/temp_password_unlock.svg"; - static const String doorlockNormalOpen = - "assets/icons/automation_functions/doorlock_normal_open.svg"; - static const String doorbell = - "assets/icons/automation_functions/doorbell.svg"; - static const String remoteUnlockViaApp = - "assets/icons/automation_functions/remote_unlock_via_app.svg"; - static const String doubleLock = - "assets/icons/automation_functions/double_lock.svg"; - static const String selfTestResult = - "assets/icons/automation_functions/self_test_result.svg"; - static const String lockAlarm = - "assets/icons/automation_functions/lock_alarm.svg"; - static const String presenceState = - "assets/icons/automation_functions/presence_state.svg"; - static const String currentTemp = - "assets/icons/automation_functions/current_temp.svg"; - static const String presence = - "assets/icons/automation_functions/presence.svg"; - static const String residualElectricity = - "assets/icons/automation_functions/residual_electricity.svg"; - static const String hijackAlarm = - "assets/icons/automation_functions/hijack_alarm.svg"; - static const String passwordUnlock = - "assets/icons/automation_functions/password_unlock.svg"; - static const String remoteUnlockRequest = - "assets/icons/automation_functions/remote_unlock_req.svg"; - static const String cardUnlock = - "assets/icons/automation_functions/card_unlock.svg"; + static const String tempPasswordUnlock = "assets/icons/automation_functions/temp_password_unlock.svg"; + static const String doorlockNormalOpen = "assets/icons/automation_functions/doorlock_normal_open.svg"; + static const String doorbell = "assets/icons/automation_functions/doorbell.svg"; + static const String remoteUnlockViaApp = "assets/icons/automation_functions/remote_unlock_via_app.svg"; + static const String doubleLock = "assets/icons/automation_functions/double_lock.svg"; + static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg"; + static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg"; + static const String presenceState = "assets/icons/automation_functions/presence_state.svg"; + static const String currentTemp = "assets/icons/automation_functions/current_temp.svg"; + static const String presence = "assets/icons/automation_functions/presence.svg"; + static const String residualElectricity = "assets/icons/automation_functions/residual_electricity.svg"; + static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg"; + static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg"; + static const String remoteUnlockRequest = "assets/icons/automation_functions/remote_unlock_req.svg"; + static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg"; static const String motion = "assets/icons/automation_functions/motion.svg"; - static const String fingerprintUnlock = - "assets/icons/automation_functions/fingerprint_unlock.svg"; + static const String fingerprintUnlock = "assets/icons/automation_functions/fingerprint_unlock.svg"; // Presence Sensor Assets static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; - static const String sensorPresenceIcon = - "assets/icons/sensor_presence_ic.svg"; + static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg"; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; - static const String illuminanceRecordIcon = - "assets/icons/illuminance_record_ic.svg"; - static const String presenceRecordIcon = - "assets/icons/presence_record_ic.svg"; - static const String helpDescriptionIcon = - "assets/icons/help_description_ic.svg"; + static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg"; + static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg"; + static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg"; static const String lightPulp = "assets/icons/light_pulb.svg"; static const String acDevice = "assets/icons/ac_device.svg"; @@ -172,4 +147,7 @@ class Assets { //assets/icons/ac_schedule.svg static const String acSchedule = 'assets/icons/ac_schedule.svg'; + + //assets/icons/preferences.svg + static const String preferences = 'assets/icons/preferences.svg'; } From 1e2e2bf4e664b13ed2cdd4c782c8a5f6b9051dfc Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 6 Oct 2024 15:09:36 +0300 Subject: [PATCH 51/65] push garage partial garage door --- .../helper/route_controls_based_code.dart | 5 + .../garage_door/bloc/garage_door_bloc.dart | 254 ++++++++++++++++++ .../garage_door/bloc/garage_door_event.dart | 165 ++++++++++++ .../garage_door/bloc/garage_door_state.dart | 129 +++++++++ .../helper/garage_door_helper.dart | 252 +++++++++++++++++ .../garage_door/models/garage_door_model.dart | 123 +++++++++ .../view/garage_door_batch_control_view.dart | 0 .../view/garage_door_control_view.dart | 177 ++++++++++++ .../widgets/schedule__garage_table.dart | 212 +++++++++++++++ .../widgets/schedule_garage_header.dart | 46 ++++ .../widgets/schedule_garage_managment_ui.dart | 50 ++++ .../widgets/schedule_garage_mode_buttons.dart | 45 ++++ .../schedule_garage_mode_selector.dart | 68 +++++ .../widgets/schedule_garage_view.dart | 84 ++++++ .../widgets/schedule_row_widget.dart | 76 ------ 15 files changed, 1610 insertions(+), 76 deletions(-) create mode 100644 lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart create mode 100644 lib/pages/device_managment/garage_door/bloc/garage_door_event.dart create mode 100644 lib/pages/device_managment/garage_door/bloc/garage_door_state.dart create mode 100644 lib/pages/device_managment/garage_door/helper/garage_door_helper.dart create mode 100644 lib/pages/device_managment/garage_door/models/garage_door_model.dart create mode 100644 lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart create mode 100644 lib/pages/device_managment/garage_door/view/garage_door_control_view.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/schedule_garage_header.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_selector.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart delete mode 100644 lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index eca980c6..3515ccf7 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart'; @@ -82,6 +83,10 @@ mixin RouteControlsBasedCode { ); case 'DS': return MainDoorSensorControlView(device: device); + case 'GD': + return GarageDoorControlView( + deviceId: device.uuid!, + ); default: return const SizedBox(); } diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart new file mode 100644 index 00000000..da93a137 --- /dev/null +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -0,0 +1,254 @@ +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_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/services/devices_mang_api.dart'; +import 'package:syncrow_web/utils/format_date_time.dart'; + +class GarageDoorBloc extends Bloc { + final String deviceId; + late GarageDoorStatusModel deviceStatus; + Timer? _timer; + + GarageDoorBloc({required this.deviceId}) : super(GarageDoorInitialState()) { + on(_fetchGarageDoorStatus); + on(_toggleGarageDoor); + on(_addSchedule); + on(_updateSchedule); + on(_deleteSchedule); + on(_fetchSchedules); + on(_increaseDelay); + on(_decreaseDelay); + on(_fetchRecords); + on(_handleUpdate); + on(_updateSelectedTime); + on(_updateSelectedDay); + on(_updateFunctionOn); + on(_initializeAddSchedule); + } + + void _fetchGarageDoorStatus(GarageDoorInitialEvent event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + var response = await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status); + emit(GarageDoorLoadedState(status: deviceStatus)); + // _listenToChanges(); + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + + void _toggleGarageDoor(ToggleGarageDoorEvent event, Emitter emit) async { + emit(GarageDoorLoadingNewState(garageDoorModel: deviceStatus)); + deviceStatus = deviceStatus.copyWith(isOpen: event.isOpen); + emit(GarageDoorLoadedState(status: deviceStatus)); + await _runDeBouncer( + deviceId: event.deviceId, + code: event.code, + value: event.isOpen, + emit: emit, + isBatch: false, + ); + } + + Future _addSchedule(AddGarageDoorScheduleEvent event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + ScheduleEntry newSchedule = ScheduleEntry( + category: event.category, + time: formatTimeOfDayToISO(event.time), + function: Status(code: 'switch_1', value: event.functionOn), + days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), + ); + + bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); + + if (success) { + add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: event.category)); + } else { + emit(GarageDoorErrorState(message: 'Failed to add schedule.')); + } + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + + Future _updateSchedule(UpdateGarageDoorScheduleEvent event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + final updatedSchedules = deviceStatus.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.enable, + uuid: deviceStatus.uuid, + scheduleId: event.scheduleId, + ); + + if (success) { + deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules); + emit(GarageDoorLoadedState(status: deviceStatus)); + } else { + emit(GarageDoorErrorState(message: 'Failed to update schedule.')); + } + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + + Future _deleteSchedule(DeleteGarageDoorScheduleEvent event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + bool success = await DevicesManagementApi().deleteScheduleRecord(deviceStatus.uuid, event.scheduleId); + + if (success) { + final updatedSchedules = + deviceStatus.schedules.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); + deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules); + emit(GarageDoorLoadedState(status: deviceStatus)); + } else { + emit(GarageDoorErrorState(message: 'Failed to delete schedule.')); + } + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + + Future _fetchSchedules(FetchGarageDoorSchedulesEvent event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + List schedules = + await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category); + + deviceStatus = deviceStatus.copyWith(schedules: schedules); + emit(GarageDoorLoadedState(status: deviceStatus)); + } catch (e) { + emit(GarageDoorErrorState(message: 'Failed to fetch schedules.')); + } + } + + void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter emit) async { + emit(GarageDoorLoadingNewState(garageDoorModel: deviceStatus)); + try { + deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10)); + emit(GarageDoorLoadedState(status: deviceStatus)); + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + + void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter emit) async { + emit(GarageDoorLoadingNewState(garageDoorModel: deviceStatus)); + try { + if (deviceStatus.delay.inMinutes > 10) { + deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10)); + } + emit(GarageDoorLoadedState(status: deviceStatus)); + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + + Future _updateSelectedTime(UpdateSelectedTimeEvent event, Emitter emit) async { + final currentState = state; + if (currentState is GarageDoorLoadedState) { + emit(currentState.copyWith(selectedTime: event.selectedTime)); + } + } + + Future _updateSelectedDay(UpdateSelectedDayEvent event, Emitter emit) async { + final currentState = state; + if (currentState is GarageDoorLoadedState) { + List updatedDays = List.from(currentState.selectedDays); + updatedDays[event.dayIndex] = event.isSelected; + emit(currentState.copyWith(selectedDays: updatedDays)); + } + } + + Future _updateFunctionOn(UpdateFunctionOnEvent event, Emitter emit) async { + final currentState = state; + if (currentState is GarageDoorLoadedState) { + emit(currentState.copyWith(functionOn: event.functionOn)); + } + } + + Future _initializeAddSchedule(InitializeAddScheduleEvent event, Emitter emit) async { + final currentState = state; + if (currentState is GarageDoorLoadedState) { + emit(currentState.copyWith( + selectedTime: event.selectedTime, + selectedDays: event.selectedDays, + functionOn: event.functionOn, + isEditing: event.isEditing, + )); + } + } + + Future _fetchRecords(FetchGarageDoorRecordsEvent event, Emitter emit) async { + emit(GarageDoorReportsLoadingState()); + try { + final DeviceReport records = await DevicesManagementApi.getDeviceReports(event.deviceId, 'code_for_records'); + emit(GarageDoorReportsState(deviceReport: records)); + } catch (e) { + emit(GarageDoorReportsFailedState(error: e.toString())); + } + } + + void _handleUpdate(GarageDoorUpdatedEvent event, Emitter emit) { + emit(GarageDoorLoadedState(status: deviceStatus)); + } + + Future _runDeBouncer({ + required dynamic deviceId, + required String code, + required dynamic value, + required Emitter emit, + required bool isBatch, + }) async { + late String id; + if (deviceId is List) { + id = deviceId.first; + } else { + id = deviceId; + } + + if (_timer != null) { + _timer!.cancel(); + } + _timer = Timer(const Duration(seconds: 1), () async { + try { + late bool response; + if (isBatch) { + response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + } else { + response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); + } + + if (!response) { + add(GarageDoorInitialEvent(id)); + } else if (response == true && code == 'scene') { + emit(GarageDoorLoadingState()); + await Future.delayed(const Duration(seconds: 1)); + add(GarageDoorInitialEvent(id)); + } + } catch (_) { + await Future.delayed(const Duration(milliseconds: 500)); + add(GarageDoorInitialEvent(id)); + } + }); + } +} diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart new file mode 100644 index 00000000..754ae140 --- /dev/null +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart @@ -0,0 +1,165 @@ +// lib/pages/device_managment/garage_door/bloc/event.dart + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +abstract class GarageDoorEvent extends Equatable { + const GarageDoorEvent(); + + @override + List get props => []; +} + +class GarageDoorInitialEvent extends GarageDoorEvent { + final String deviceId; + + const GarageDoorInitialEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class ToggleGarageDoorEvent extends GarageDoorEvent { + final String deviceId; + final bool isOpen; + final String code; + + const ToggleGarageDoorEvent({required this.deviceId, required this.isOpen, required this.code}); + + @override + List get props => [deviceId, isOpen]; +} + +class AddGarageDoorScheduleEvent extends GarageDoorEvent { + final String category; + final TimeOfDay time; + final bool functionOn; + final List selectedDays; + + const AddGarageDoorScheduleEvent({ + required this.category, + required this.time, + required this.functionOn, + required this.selectedDays, + }); +} + +class UpdateGarageDoorScheduleEvent extends GarageDoorEvent { + final String deviceId; + final String scheduleId; + final bool enable; + final bool functionOn; + final int index; + + const UpdateGarageDoorScheduleEvent({ + required this.deviceId, + required this.scheduleId, + required this.enable, + required this.functionOn, + required this.index, + }); +} + +class DeleteGarageDoorScheduleEvent extends GarageDoorEvent { + final String deviceId; + final String scheduleId; + final int index; + + const DeleteGarageDoorScheduleEvent({ + required this.deviceId, + required this.scheduleId, + required this.index, + }); +} + +class FetchGarageDoorSchedulesEvent extends GarageDoorEvent { + final String deviceId; + final String category; + + const FetchGarageDoorSchedulesEvent({ + required this.deviceId, + required this.category, + }); +} + +class IncreaseGarageDoorDelayEvent extends GarageDoorEvent { + final String deviceId; + + const IncreaseGarageDoorDelayEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class DecreaseGarageDoorDelayEvent extends GarageDoorEvent { + final String deviceId; + + const DecreaseGarageDoorDelayEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class FetchGarageDoorRecordsEvent extends GarageDoorEvent { + final String deviceId; + + const FetchGarageDoorRecordsEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class GarageDoorUpdatedEvent extends GarageDoorEvent {} + +class UpdateSelectedTimeEvent extends GarageDoorEvent { + final TimeOfDay? selectedTime; + + const UpdateSelectedTimeEvent(this.selectedTime); + + @override + List get props => [selectedTime]; +} + +class UpdateSelectedDayEvent extends GarageDoorEvent { + final int dayIndex; + final bool isSelected; + + const UpdateSelectedDayEvent(this.dayIndex, this.isSelected); + + @override + List get props => [dayIndex, isSelected]; +} + +class UpdateFunctionOnEvent extends GarageDoorEvent { + final bool functionOn; + + const UpdateFunctionOnEvent({required this.functionOn}); + + @override + List get props => [functionOn]; +} + +class InitializeAddScheduleEvent extends GarageDoorEvent { + final TimeOfDay? selectedTime; + final List selectedDays; + final bool functionOn; + final bool isEditing; + final int? index; + + const InitializeAddScheduleEvent({ + required this.selectedTime, + required this.selectedDays, + required this.functionOn, + required this.isEditing, + this.index, + }); + + @override + List get props => [ + selectedTime, + selectedDays, + functionOn, + isEditing, + index, + ]; +} diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart new file mode 100644 index 00000000..24b07618 --- /dev/null +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart @@ -0,0 +1,129 @@ +// lib/pages/device_managment/garage_door/bloc/state.dart + +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.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'; + +abstract class GarageDoorState extends Equatable { + const GarageDoorState(); + + @override + List get props => []; +} + +class GarageDoorInitialState extends GarageDoorState {} + +class GarageDoorLoadingState extends GarageDoorState {} + +class GarageDoorLoadedState extends GarageDoorState { + final GarageDoorStatusModel status; + final List? schedules; + final Duration? delay; + final DeviceReport? records; + final List selectedDays; + final TimeOfDay? selectedTime; + final bool functionOn; + final bool isEditing; + final ScheduleModes? scheduleMode; + + const GarageDoorLoadedState({ + required this.status, + this.schedules, + this.delay, + this.records, + this.selectedDays = const [false, false, false, false, false, false, false], + this.selectedTime, + this.functionOn = false, + this.isEditing = false, + this.scheduleMode = ScheduleModes.schedule, + }); + + @override + List get props => [ + status, + schedules, + delay, + records, + selectedDays, + selectedTime, + functionOn, + isEditing, + scheduleMode, + ]; + + GarageDoorLoadedState copyWith({ + GarageDoorStatusModel? status, + List? schedules, + Duration? delay, + DeviceReport? records, + List? selectedDays, + TimeOfDay? selectedTime, + bool? functionOn, + bool? isEditing, + ScheduleModes? scheduleMode, + }) { + return GarageDoorLoadedState( + status: status ?? this.status, + schedules: schedules ?? this.schedules, + delay: delay ?? this.delay, + records: records ?? this.records, + selectedDays: selectedDays ?? this.selectedDays, + selectedTime: selectedTime, + functionOn: functionOn ?? this.functionOn, + isEditing: isEditing ?? this.isEditing, + scheduleMode: scheduleMode ?? this.scheduleMode, + ); + } +} + +class GarageDoorErrorState extends GarageDoorState { + final String message; + + const GarageDoorErrorState({required this.message}); + + @override + List get props => [message]; +} + +class GarageDoorLoadingNewState extends GarageDoorState { + final GarageDoorStatusModel garageDoorModel; + + const GarageDoorLoadingNewState({required this.garageDoorModel}); + + @override + List get props => [garageDoorModel]; +} + +class GarageDoorReportsLoadingState extends GarageDoorState {} + +class GarageDoorReportsFailedState extends GarageDoorState { + final String error; + + const GarageDoorReportsFailedState({required this.error}); + + @override + List get props => [error]; +} + +class GarageDoorReportsState extends GarageDoorState { + final DeviceReport deviceReport; + + const GarageDoorReportsState({required this.deviceReport}); + + @override + List get props => [deviceReport]; +} + +class ShowGarageDoorDescriptionState extends GarageDoorState { + final String description; + + const ShowGarageDoorDescriptionState({required this.description}); + + @override + List get props => [description]; +} + +class ScheduleGarageLoadingState extends GarageDoorState {} diff --git a/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart b/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart new file mode 100644 index 00000000..e939ad9e --- /dev/null +++ b/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart @@ -0,0 +1,252 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class ScheduleGarageDoorDialogHelper { + static void showAddGarageDoorScheduleDialog(BuildContext context, + {ScheduleModel? schedule, int? index, bool? isEdit}) { + final bloc = context.read(); + + if (schedule == null) { + bloc.add((const UpdateSelectedTimeEvent(null))); + bloc.add(InitializeAddScheduleEvent( + selectedTime: null, + selectedDays: List.filled(7, false), + functionOn: false, + isEditing: false, + )); + } else { + final time = _convertStringToTimeOfDay(schedule.time); + final selectedDays = _convertDaysStringToBooleans(schedule.days); + + bloc.add(InitializeAddScheduleEvent( + selectedTime: time, + selectedDays: selectedDays, + functionOn: schedule.function.value, + isEditing: true, + index: index, + )); + } + + showDialog( + context: context, + builder: (ctx) { + return BlocProvider.value( + value: bloc, + child: BlocBuilder( + builder: (context, state) { + if (state is GarageDoorLoadedState) { + 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: isEdit == true + ? null + : () async { + TimeOfDay? time = await showTimePicker( + context: context, + initialTime: state.selectedTime ?? TimeOfDay.now(), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: const ColorScheme.light( + primary: ColorsManager.primaryColor, + ), + ), + child: child!, + ); + }, + ); + 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, isEdit: isEdit), + const SizedBox(height: 16), + _buildFunctionSwitch(context, state.functionOn, isEdit), + ], + ), + 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) { + return; + } else { + bloc.add(AddGarageDoorScheduleEvent( + category: 'switch_1', + time: state.selectedTime!, + selectedDays: state.selectedDays, + functionOn: state.functionOn, + )); + } + Navigator.pop(context); + } + }, + backgroundColor: ColorsManager.primaryColor, + child: const Text('Save'), + ), + ), + ], + ); + } + return const SizedBox(); + }, + ), + ); + }, + ); + } + + static TimeOfDay _convertStringToTimeOfDay(String timeString) { + 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 _convertDaysStringToBooleans(List selectedDays) { + final daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + List daysBoolean = List.filled(7, false); + + for (int i = 0; i < daysOfWeek.length; i++) { + if (selectedDays.contains(daysOfWeek[i])) { + daysBoolean[i] = true; + } + } + + return daysBoolean; + } + + static Widget _buildDayCheckboxes(BuildContext context, List selectedDays, {bool? isEdit}) { + final dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + + return Row( + children: List.generate(7, (index) { + return Row( + children: [ + Checkbox( + value: selectedDays[index], + onChanged: isEdit == true + ? null + : (bool? value) { + context.read().add(UpdateSelectedDayEvent(index, value!)); + }, + ), + Text(dayLabels[index]), + ], + ); + }), + ); + } + + static Widget _buildFunctionSwitch(BuildContext context, bool isOn, bool? isEdit) { + return Row( + children: [ + Text( + 'Function:', + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.grayColor), + ), + const SizedBox(width: 10), + Radio( + value: true, + groupValue: isOn, + onChanged: (bool? value) { + if (isEdit == true) { + return; + } else { + context.read().add(const UpdateFunctionOnEvent(functionOn: true)); + } + }, + ), + const Text('On'), + const SizedBox(width: 10), + Radio( + value: false, + groupValue: isOn, + onChanged: (bool? value) { + if (isEdit == true) { + return; + } else { + context.read().add(const UpdateFunctionOnEvent(functionOn: false)); + } + }, + ), + const Text('Off'), + ], + ); + } +} diff --git a/lib/pages/device_managment/garage_door/models/garage_door_model.dart b/lib/pages/device_managment/garage_door/models/garage_door_model.dart new file mode 100644 index 00000000..9e8d75fe --- /dev/null +++ b/lib/pages/device_managment/garage_door/models/garage_door_model.dart @@ -0,0 +1,123 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart'; + +class GarageDoorStatusModel { + final String uuid; + final bool switch1; + final int countdown1; + final bool doorContactState; + final int trTimeCon; + final int countdownAlarm; + final String doorControl1; + final bool voiceControl1; + final String doorState1; + final bool isOpen; + final Duration delay; + final List schedules; // Add schedules field + + GarageDoorStatusModel({ + required this.uuid, + required this.switch1, + required this.countdown1, + required this.doorContactState, + required this.trTimeCon, + required this.countdownAlarm, + required this.doorControl1, + required this.voiceControl1, + required this.doorState1, + required this.isOpen, + required this.delay, + required this.schedules, // Initialize schedules + }); + + factory GarageDoorStatusModel.fromJson(String id, List jsonList) { + late bool switch1; + late int countdown1; + late bool doorContactState; + late int trTimeCon; + late int countdownAlarm; + late String doorControl1; + late bool voiceControl1; + late String doorState1; + List schedules = []; // Initialize schedules + + for (var status in jsonList) { + switch (status.code) { + case 'switch_1': + switch1 = status.value ?? false; + break; + case 'countdown_1': + countdown1 = status.value ?? 0; + break; + case 'doorcontact_state': + doorContactState = status.value ?? false; + break; + case 'tr_timecon': + trTimeCon = status.value ?? 0; + break; + case 'countdown_alarm': + countdownAlarm = status.value ?? 0; + break; + case 'door_control_1': + doorControl1 = status.value ?? 'closed'; + break; + case 'voice_control_1': + voiceControl1 = status.value ?? false; + break; + case 'door_state_1': + doorState1 = status.value ?? 'closed'; + break; + } + } + + return GarageDoorStatusModel( + uuid: id, + switch1: switch1, + countdown1: countdown1, + doorContactState: doorContactState, + trTimeCon: trTimeCon, + countdownAlarm: countdownAlarm, + doorControl1: doorControl1, + voiceControl1: voiceControl1, + doorState1: doorState1, + isOpen: doorState1 == 'open' ? true : false, + delay: Duration(seconds: countdown1), + schedules: schedules, // Assign schedules + ); + } + + GarageDoorStatusModel copyWith({ + String? uuid, + bool? switch1, + int? countdown1, + bool? doorContactState, + int? trTimeCon, + int? countdownAlarm, + String? doorControl1, + bool? voiceControl1, + String? doorState1, + bool? isOpen, + Duration? delay, + List? schedules, // Add schedules to copyWith + }) { + return GarageDoorStatusModel( + uuid: uuid ?? this.uuid, + switch1: switch1 ?? this.switch1, + countdown1: countdown1 ?? this.countdown1, + doorContactState: doorContactState ?? this.doorContactState, + trTimeCon: trTimeCon ?? this.trTimeCon, + countdownAlarm: countdownAlarm ?? this.countdownAlarm, + doorControl1: doorControl1 ?? this.doorControl1, + voiceControl1: voiceControl1 ?? this.voiceControl1, + doorState1: doorState1 ?? this.doorState1, + isOpen: isOpen ?? this.isOpen, + delay: delay ?? this.delay, + schedules: schedules ?? this.schedules, // Copy schedules + ); + } + + @override + String toString() { + return 'GarageDoorStatusModel(uuid: $uuid, switch1: $switch1, countdown1: $countdown1, doorContactState: $doorContactState, trTimeCon: $trTimeCon, countdownAlarm: $countdownAlarm, doorControl1: $doorControl1, voiceControl1: $voiceControl1, doorState1: $doorState1, isOpen: $isOpen, delay: $delay, schedules: $schedules)'; + } +} diff --git a/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart b/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart new file mode 100644 index 00000000..8e6a62f0 --- /dev/null +++ b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart @@ -0,0 +1,177 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_view.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.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/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 '../../main_door_sensor/view/main_door_control_view.dart'; + +class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout { + final String deviceId; + + const GarageDoorControlView({required this.deviceId, super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Garage Door Control')), + body: BlocProvider( + create: (context) => GarageDoorBloc(deviceId: deviceId)..add(GarageDoorInitialEvent(deviceId)), + child: BlocBuilder( + builder: (context, state) { + if (state is GarageDoorLoadingState || state is GarageDoorLoadingNewState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is GarageDoorLoadedState) { + return _buildControlView(context, state.status); + } else if (state is GarageDoorErrorState) { + return Center(child: Text('Error: ${state.message}')); + } + return const Center(child: Text('Unknown state')); + }, + ), + ), + ); + } + + Widget _buildControlView(BuildContext context, GarageDoorStatusModel status) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + + return GridView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge || isExtraLarge + ? 3 + : isMedium + ? 2 + : 1, + childAspectRatio: 1.5, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + IconNameStatusContainer( + isFullIcon: true, + name: status.doorContactState ? 'Open' : 'Close', + icon: Assets.openCloseDoor, + onTap: () { + context.read().add( + ToggleGarageDoorEvent(deviceId: status.uuid, isOpen: !status.isOpen, code: 'switch_1'), + ); + }, + status: status.doorContactState, + textColor: status.doorContactState ? ColorsManager.red : ColorsManager.blackColor, + paddingAmount: 8, + ), + GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: BlocProvider.of(context), + child: BuildGarageDoorScheduleView(status: status), + )); + }, + child: DeviceControlsContainer( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 60, + height: 60, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, + ), + padding: const EdgeInsets.all(12), + child: ClipOval( + child: SvgPicture.asset( + Assets.scheduling, + fit: BoxFit.fill, + ), + ), + ), + const SizedBox(height: 8), + Text( + 'Scheduling', + textAlign: TextAlign.center, + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + ), + ), + ], + ), + ), + ), + ToggleWidget( + label: '', + labelWidget: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + onPressed: () { + context.read().add(DecreaseGarageDoorDelayEvent(deviceId: deviceId)); + }, + icon: const Icon( + Icons.remove, + size: 28, + color: ColorsManager.greyColor, + ), + ), + Text( + '${status.delay.inHours.toString().padLeft(2, '0')}', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'h', + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), + ), + Text( + '${(status.delay.inMinutes % 60).toString().padLeft(2, '0')}', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'm', + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), + ), + IconButton( + onPressed: () { + context.read().add(IncreaseGarageDoorDelayEvent(deviceId: deviceId)); + }, + icon: const Icon( + Icons.add, + size: 28, + color: ColorsManager.greyColor, + ), + ), + ], + ), + value: false, + code: 'switch_1', + deviceId: status.uuid, + icon: Assets.acSchedule, + onChange: (value) {}, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart new file mode 100644 index 00000000..4bd54aa8 --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart @@ -0,0 +1,212 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_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'; +import 'package:syncrow_web/utils/format_date_time.dart'; + +class ScheduleGarageTableWidget extends StatelessWidget { + final GarageDoorLoadedState state; + + const ScheduleGarageTableWidget({ + super.key, + required this.state, + }); + + @override + Widget build(BuildContext context) { + 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'), + ], + ), + ], + ), + BlocBuilder( + builder: (context, state) { + if (state is ScheduleGarageLoadingState) { + return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator())); + } + if (state is GarageDoorLoadedState && state.schedules?.isEmpty == true) { + return _buildEmptyState(context); + } else if (state is GarageDoorLoadedState) { + return Container( + height: 200, + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.graysColor), + borderRadius: + const BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), + ), + child: _buildTableBody(state, context)); + } + return const SizedBox( + height: 200, + ); + }, + ), + ], + ); + } + + Widget _buildEmptyState(BuildContext context) { + return Container( + height: 200, + decoration: BoxDecoration( + border: Border.all(color: ColorsManager.graysColor), + borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), + ), + child: 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(GarageDoorLoadedState state, BuildContext context) { + return SizedBox( + height: 200, + child: SingleChildScrollView( + child: Table( + border: TableBorder.all(color: ColorsManager.graysColor), + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + children: [ + if (state.schedules != null) + for (int i = 0; i < state.schedules!.length; i++) + _buildScheduleRow(state.schedules![i], i, context, state), + ], + ), + ), + ); + } + + Widget _buildTableHeader(String label) { + return TableCell( + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + label, + style: const TextStyle( + fontSize: 13, + color: ColorsManager.grayColor, + ), + ), + ), + ); + } + + TableRow _buildScheduleRow(ScheduleModel schedule, int index, BuildContext context, GarageDoorLoadedState state) { + return TableRow( + children: [ + Center( + child: GestureDetector( + onTap: () { + context.read().add(UpdateGarageDoorScheduleEvent( + 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, + color: ColorsManager.grayColor, + ), + ), + ), + ), + Center(child: Text(_getSelectedDays(ScheduleModel.parseSelectedDays(schedule.days)))), + Center(child: Text(formatIsoStringToTime(schedule.time, context))), + Center(child: Text(schedule.function.value ? 'On' : 'Off')), + Center( + child: Wrap( + runAlignment: WrapAlignment.center, + children: [ + TextButton( + style: TextButton.styleFrom(padding: EdgeInsets.zero), + onPressed: () { + ScheduleGarageDoorDialogHelper.showAddGarageDoorScheduleDialog(context, + schedule: schedule, index: index, isEdit: true); + }, + child: Text( + 'Edit', + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blueColor), + ), + ), + TextButton( + style: TextButton.styleFrom(padding: EdgeInsets.zero), + onPressed: () { + context.read().add(DeleteGarageDoorScheduleEvent( + index: index, + scheduleId: schedule.scheduleId, + deviceId: state.status.uuid, + )); + }, + child: Text( + 'Delete', + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blueColor), + ), + ), + ], + ), + ), + ], + ); + } + + String _getSelectedDays(List selectedDays) { + final days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; + List selectedDaysStr = []; + for (int i = 0; i < selectedDays.length; i++) { + if (selectedDays[i]) { + selectedDaysStr.add(days[i]); + } + } + return selectedDaysStr.join(', '); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_header.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_header.dart new file mode 100644 index 00000000..cf42e6a3 --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_header.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class ScheduleGarageHeader extends StatelessWidget { + const ScheduleGarageHeader({super.key}); + + @override + Widget build(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: const EdgeInsets.all(1), + icon: const Icon( + Icons.close, + color: Colors.grey, + size: 18, + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart new file mode 100644 index 00000000..e5819e89 --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule__garage_table.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class ScheduleGarageManagementUI extends StatelessWidget { + final GarageDoorLoadedState state; + final Function onAddSchedule; + + const ScheduleGarageManagementUI({ + super.key, + required this.state, + required this.onAddSchedule, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 170, + height: 40, + child: DefaultButton( + borderColor: ColorsManager.boxColor, + padding: 2, + backgroundColor: ColorsManager.graysColor, + borderRadius: 15, + onPressed: () => onAddSchedule(), + 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), + ScheduleGarageTableWidget(state: state), + ], + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart new file mode 100644 index 00000000..f1307d5f --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class ScheduleModeButtons extends StatelessWidget { + final VoidCallback onSave; + + const ScheduleModeButtons({ + super.key, + required this.onSave, + }); + + @override + Widget build(BuildContext context) { + return 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: DefaultButton( + height: 40, + onPressed: onSave, + backgroundColor: ColorsManager.primaryColor, + child: const Text('Save'), + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_selector.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_selector.dart new file mode 100644 index 00000000..7b6e4690 --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_selector.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.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/extension/build_context_x.dart'; + +class ScheduleGarageDoorModeSelector extends StatelessWidget { + final GarageDoorLoadedState state; + + const ScheduleGarageDoorModeSelector({super.key, required this.state}); + + @override + Widget build(BuildContext context) { + 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, 'Schedule', ScheduleModes.schedule, state), + ], + ), + ], + ); + } + + Widget _buildRadioTile(BuildContext context, String label, ScheduleModes mode, GarageDoorLoadedState state) { + return Flexible( + child: ListTile( + contentPadding: EdgeInsets.zero, + title: Text( + label, + style: context.textTheme.bodySmall!.copyWith( + fontSize: 13, + color: ColorsManager.blackColor, + ), + ), + leading: Radio( + value: mode, + groupValue: state.scheduleMode, + onChanged: (ScheduleModes? value) { + if (value != null) { + if (value == ScheduleModes.schedule) { + context.read().add( + FetchGarageDoorSchedulesEvent( + category: 'switch_1', + deviceId: state.status.uuid, + ), + ); + } + } + }, + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart new file mode 100644 index 00000000..be11203f --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_header.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_mode_selector.dart'; + +class BuildGarageDoorScheduleView extends StatefulWidget { + const BuildGarageDoorScheduleView({super.key, required this.status}); + + final GarageDoorStatusModel status; + + @override + State createState() => _BuildScheduleViewState(); +} + +class _BuildScheduleViewState extends State { + @override + Widget build(BuildContext context) { + final bloc = BlocProvider.of(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( + builder: (context, state) { + if (state is GarageDoorLoadedState) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const ScheduleGarageHeader(), + const SizedBox(height: 20), + ScheduleGarageDoorModeSelector(state: state), + const SizedBox(height: 20), + ScheduleGarageManagementUI( + state: state, + onAddSchedule: () { + ScheduleGarageDoorDialogHelper.showAddGarageDoorScheduleDialog(context, + schedule: null, index: null, isEdit: false); + }, + ), + ], + ); + } + if (state is GarageDoorLoadingState) { + return const SizedBox( + height: 200, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ScheduleGarageHeader(), + SizedBox( + height: 20, + ), + Center(child: CircularProgressIndicator()), + ], + )); + } + return const SizedBox( + height: 200, + child: ScheduleGarageHeader(), + ); + }, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart b/lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart deleted file mode 100644 index ee0805d6..00000000 --- a/lib/pages/device_managment/water_heater/widgets/schedule_row_widget.dart +++ /dev/null @@ -1,76 +0,0 @@ -// 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; - -// 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.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 selectedDays) { -// final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; -// List selectedDaysStr = []; -// for (int i = 0; i < selectedDays.length; i++) { -// if (selectedDays[i]) { -// selectedDaysStr.add(days[i]); -// } -// } -// return selectedDaysStr.join(', '); -// } -// } From 4f833e86fb24a114f746b1cfca2ad60e2c4f2c6a Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 6 Oct 2024 23:31:36 +0300 Subject: [PATCH 52/65] push garage door schedule --- assets/icons/closed_door.svg | 135 ++++++++++++++++ assets/icons/door_delay.svg | 29 ++++ assets/icons/opened_door.svg | 104 ++++++++++++ assets/icons/records.svg | 18 +++ .../garage_door/bloc/garage_door_bloc.dart | 152 +++++++++++------- .../garage_door/bloc/garage_door_state.dart | 6 - .../garage_door/models/garage_door_model.dart | 14 +- .../view/garage_door_control_view.dart | 119 +++++++------- .../widgets/schedule__garage_table.dart | 8 +- .../widgets/schedule_garage_mode_buttons.dart | 4 +- .../widgets/schedule_garage_view.dart | 26 ++- .../view/main_door_control_view.dart | 52 +++--- .../shared/device_control_dialog.dart | 13 +- lib/utils/constants/assets.dart | 5 + 14 files changed, 494 insertions(+), 191 deletions(-) create mode 100644 assets/icons/closed_door.svg create mode 100644 assets/icons/door_delay.svg create mode 100644 assets/icons/opened_door.svg create mode 100644 assets/icons/records.svg diff --git a/assets/icons/closed_door.svg b/assets/icons/closed_door.svg new file mode 100644 index 00000000..9cbf40dc --- /dev/null +++ b/assets/icons/closed_door.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/door_delay.svg b/assets/icons/door_delay.svg new file mode 100644 index 00000000..49dbbaef --- /dev/null +++ b/assets/icons/door_delay.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/opened_door.svg b/assets/icons/opened_door.svg new file mode 100644 index 00000000..386a66f1 --- /dev/null +++ b/assets/icons/opened_door.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/records.svg b/assets/icons/records.svg new file mode 100644 index 00000000..9e316afd --- /dev/null +++ b/assets/icons/records.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index da93a137..10ccbca4 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_ import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_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/device_managment/water_heater/models/water_heater_status_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/utils/format_date_time.dart'; @@ -39,27 +40,29 @@ class GarageDoorBloc extends Bloc { var response = await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status); emit(GarageDoorLoadedState(status: deviceStatus)); - // _listenToChanges(); } catch (e) { emit(GarageDoorErrorState(message: e.toString())); } } void _toggleGarageDoor(ToggleGarageDoorEvent event, Emitter emit) async { - emit(GarageDoorLoadingNewState(garageDoorModel: deviceStatus)); - deviceStatus = deviceStatus.copyWith(isOpen: event.isOpen); + final oldValue = deviceStatus.switch1; + _updateLocalValue('switch_1', event.isOpen); emit(GarageDoorLoadedState(status: deviceStatus)); - await _runDeBouncer( + final success = await _runDeBouncer( deviceId: event.deviceId, - code: event.code, + code: 'switch_1', value: event.isOpen, + oldValue: oldValue, emit: emit, isBatch: false, ); + if (!success) { + _revertValue('switch_1', oldValue, emit); + } } Future _addSchedule(AddGarageDoorScheduleEvent event, Emitter emit) async { - emit(GarageDoorLoadingState()); try { ScheduleEntry newSchedule = ScheduleEntry( category: event.category, @@ -67,23 +70,20 @@ class GarageDoorBloc extends Bloc { function: Status(code: 'switch_1', value: event.functionOn), days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), ); - bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); - if (success) { - add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: event.category)); + add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1')); } else { - emit(GarageDoorErrorState(message: 'Failed to add schedule.')); + emit(GarageDoorLoadedState(status: deviceStatus)); } } catch (e) { - emit(GarageDoorErrorState(message: e.toString())); + emit(GarageDoorLoadedState(status: deviceStatus)); } } Future _updateSchedule(UpdateGarageDoorScheduleEvent event, Emitter emit) async { - emit(GarageDoorLoadingState()); try { - final updatedSchedules = deviceStatus.schedules.map((schedule) { + final updatedSchedules = deviceStatus.schedules?.map((schedule) { if (schedule.scheduleId == event.scheduleId) { return schedule.copyWith( function: Status(code: 'switch_1', value: event.functionOn), @@ -92,57 +92,61 @@ class GarageDoorBloc extends Bloc { } return schedule; }).toList(); - bool success = await DevicesManagementApi().updateScheduleRecord( enable: event.enable, uuid: deviceStatus.uuid, scheduleId: event.scheduleId, ); - if (success) { deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules); emit(GarageDoorLoadedState(status: deviceStatus)); } else { - emit(GarageDoorErrorState(message: 'Failed to update schedule.')); + emit(GarageDoorLoadedState(status: deviceStatus)); } } catch (e) { - emit(GarageDoorErrorState(message: e.toString())); + emit(GarageDoorLoadedState(status: deviceStatus)); } } Future _deleteSchedule(DeleteGarageDoorScheduleEvent event, Emitter emit) async { - emit(GarageDoorLoadingState()); try { bool success = await DevicesManagementApi().deleteScheduleRecord(deviceStatus.uuid, event.scheduleId); - if (success) { final updatedSchedules = - deviceStatus.schedules.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); + deviceStatus.schedules?.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules); emit(GarageDoorLoadedState(status: deviceStatus)); } else { - emit(GarageDoorErrorState(message: 'Failed to delete schedule.')); + emit(GarageDoorLoadedState(status: deviceStatus)); } } catch (e) { - emit(GarageDoorErrorState(message: e.toString())); + emit(GarageDoorLoadedState(status: deviceStatus)); } } Future _fetchSchedules(FetchGarageDoorSchedulesEvent event, Emitter emit) async { - emit(GarageDoorLoadingState()); + emit(ScheduleGarageLoadingState()); try { List schedules = await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category); - deviceStatus = deviceStatus.copyWith(schedules: schedules); - emit(GarageDoorLoadedState(status: deviceStatus)); + emit( + GarageDoorLoadedState( + status: deviceStatus, + scheduleMode: ScheduleModes.schedule, + ), + ); } catch (e) { - emit(GarageDoorErrorState(message: 'Failed to fetch schedules.')); + emit( + GarageDoorLoadedState( + status: deviceStatus, + scheduleMode: ScheduleModes.schedule, + ), + ); } } void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter emit) async { - emit(GarageDoorLoadingNewState(garageDoorModel: deviceStatus)); try { deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10)); emit(GarageDoorLoadedState(status: deviceStatus)); @@ -152,7 +156,6 @@ class GarageDoorBloc extends Bloc { } void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter emit) async { - emit(GarageDoorLoadingNewState(garageDoorModel: deviceStatus)); try { if (deviceStatus.delay.inMinutes > 10) { deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10)); @@ -175,14 +178,14 @@ class GarageDoorBloc extends Bloc { if (currentState is GarageDoorLoadedState) { List updatedDays = List.from(currentState.selectedDays); updatedDays[event.dayIndex] = event.isSelected; - emit(currentState.copyWith(selectedDays: updatedDays)); + emit(currentState.copyWith(selectedDays: updatedDays, selectedTime: currentState.selectedTime)); } } Future _updateFunctionOn(UpdateFunctionOnEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { - emit(currentState.copyWith(functionOn: event.functionOn)); + emit(currentState.copyWith(functionOn: event.functionOn, selectedTime: currentState.selectedTime)); } } @@ -201,7 +204,7 @@ class GarageDoorBloc extends Bloc { Future _fetchRecords(FetchGarageDoorRecordsEvent event, Emitter emit) async { emit(GarageDoorReportsLoadingState()); try { - final DeviceReport records = await DevicesManagementApi.getDeviceReports(event.deviceId, 'code_for_records'); + final DeviceReport records = await DevicesManagementApi.getDeviceReports(event.deviceId, 'switch_1'); emit(GarageDoorReportsState(deviceReport: records)); } catch (e) { emit(GarageDoorReportsFailedState(error: e.toString())); @@ -212,43 +215,68 @@ class GarageDoorBloc extends Bloc { emit(GarageDoorLoadedState(status: deviceStatus)); } - Future _runDeBouncer({ + Future _runDeBouncer({ required dynamic deviceId, required String code, required dynamic value, + required dynamic oldValue, required Emitter emit, required bool isBatch, }) async { - late String id; - if (deviceId is List) { - id = deviceId.first; - } else { - id = deviceId; - } - - if (_timer != null) { - _timer!.cancel(); - } - _timer = Timer(const Duration(seconds: 1), () async { - try { - late bool response; - if (isBatch) { - response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); - } else { - response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); - } - - if (!response) { - add(GarageDoorInitialEvent(id)); - } else if (response == true && code == 'scene') { - emit(GarageDoorLoadingState()); - await Future.delayed(const Duration(seconds: 1)); - add(GarageDoorInitialEvent(id)); - } - } catch (_) { - await Future.delayed(const Duration(milliseconds: 500)); - add(GarageDoorInitialEvent(id)); + try { + late bool status; + await Future.delayed(const Duration(milliseconds: 500)); + if (isBatch) { + status = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + } else { + status = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); } - }); + + if (!status) { + _revertValue(code, oldValue, emit); + return false; + } else { + return true; + } + } catch (e) { + _revertValue(code, oldValue, emit); + return false; + } + } + + void _revertValue(String code, dynamic oldValue, Emitter emit) { + switch (code) { + case 'switch_1': + if (oldValue is bool) { + deviceStatus = deviceStatus.copyWith(switch1: oldValue); + } + break; + // Add other cases if needed + default: + break; + } + if (state is GarageDoorLoadedState) { + final currentState = state as GarageDoorLoadedState; + emit(currentState.copyWith(status: deviceStatus)); + } + } + + void _updateLocalValue(String code, dynamic value) { + switch (code) { + case 'switch_1': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(switch1: value); + } + break; + // Add other cases if needed + default: + break; + } + } + + @override + Future close() { + _timer?.cancel(); + return super.close(); } } diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart index 24b07618..6f3a2320 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart @@ -4,7 +4,6 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.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'; abstract class GarageDoorState extends Equatable { @@ -20,7 +19,6 @@ class GarageDoorLoadingState extends GarageDoorState {} class GarageDoorLoadedState extends GarageDoorState { final GarageDoorStatusModel status; - final List? schedules; final Duration? delay; final DeviceReport? records; final List selectedDays; @@ -31,7 +29,6 @@ class GarageDoorLoadedState extends GarageDoorState { const GarageDoorLoadedState({ required this.status, - this.schedules, this.delay, this.records, this.selectedDays = const [false, false, false, false, false, false, false], @@ -44,7 +41,6 @@ class GarageDoorLoadedState extends GarageDoorState { @override List get props => [ status, - schedules, delay, records, selectedDays, @@ -56,7 +52,6 @@ class GarageDoorLoadedState extends GarageDoorState { GarageDoorLoadedState copyWith({ GarageDoorStatusModel? status, - List? schedules, Duration? delay, DeviceReport? records, List? selectedDays, @@ -67,7 +62,6 @@ class GarageDoorLoadedState extends GarageDoorState { }) { return GarageDoorLoadedState( status: status ?? this.status, - schedules: schedules ?? this.schedules, delay: delay ?? this.delay, records: records ?? this.records, selectedDays: selectedDays ?? this.selectedDays, diff --git a/lib/pages/device_managment/garage_door/models/garage_door_model.dart b/lib/pages/device_managment/garage_door/models/garage_door_model.dart index 9e8d75fe..dcb4718c 100644 --- a/lib/pages/device_managment/garage_door/models/garage_door_model.dart +++ b/lib/pages/device_managment/garage_door/models/garage_door_model.dart @@ -11,9 +11,9 @@ class GarageDoorStatusModel { final String doorControl1; final bool voiceControl1; final String doorState1; - final bool isOpen; + // final bool isOpen; final Duration delay; - final List schedules; // Add schedules field + final List? schedules; // Add schedules field GarageDoorStatusModel({ required this.uuid, @@ -25,7 +25,7 @@ class GarageDoorStatusModel { required this.doorControl1, required this.voiceControl1, required this.doorState1, - required this.isOpen, + // required this.isOpen, required this.delay, required this.schedules, // Initialize schedules }); @@ -80,7 +80,7 @@ class GarageDoorStatusModel { doorControl1: doorControl1, voiceControl1: voiceControl1, doorState1: doorState1, - isOpen: doorState1 == 'open' ? true : false, + // isOpen: doorState1 == 'open' ? true : false, delay: Duration(seconds: countdown1), schedules: schedules, // Assign schedules ); @@ -96,7 +96,7 @@ class GarageDoorStatusModel { String? doorControl1, bool? voiceControl1, String? doorState1, - bool? isOpen, + // bool? isOpen, Duration? delay, List? schedules, // Add schedules to copyWith }) { @@ -110,7 +110,7 @@ class GarageDoorStatusModel { doorControl1: doorControl1 ?? this.doorControl1, voiceControl1: voiceControl1 ?? this.voiceControl1, doorState1: doorState1 ?? this.doorState1, - isOpen: isOpen ?? this.isOpen, + // isOpen: isOpen ?? this.isOpen, delay: delay ?? this.delay, schedules: schedules ?? this.schedules, // Copy schedules ); @@ -118,6 +118,6 @@ class GarageDoorStatusModel { @override String toString() { - return 'GarageDoorStatusModel(uuid: $uuid, switch1: $switch1, countdown1: $countdown1, doorContactState: $doorContactState, trTimeCon: $trTimeCon, countdownAlarm: $countdownAlarm, doorControl1: $doorControl1, voiceControl1: $voiceControl1, doorState1: $doorState1, isOpen: $isOpen, delay: $delay, schedules: $schedules)'; + return 'GarageDoorStatusModel(uuid: $uuid, switch1: $switch1, countdown1: $countdown1, doorContactState: $doorContactState, trTimeCon: $trTimeCon, countdownAlarm: $countdownAlarm, doorControl1: $doorControl1, voiceControl1: $voiceControl1, doorState1: $doorState1, delay: $delay, schedules: $schedules)'; } } diff --git a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart index 8e6a62f0..8e6201f7 100644 --- a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart +++ b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_view.dart'; -import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.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/constants/assets.dart'; @@ -22,22 +20,19 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Garage Door Control')), - body: BlocProvider( - create: (context) => GarageDoorBloc(deviceId: deviceId)..add(GarageDoorInitialEvent(deviceId)), - child: BlocBuilder( - builder: (context, state) { - if (state is GarageDoorLoadingState || state is GarageDoorLoadingNewState) { - return const Center(child: CircularProgressIndicator()); - } else if (state is GarageDoorLoadedState) { - return _buildControlView(context, state.status); - } else if (state is GarageDoorErrorState) { - return Center(child: Text('Error: ${state.message}')); - } - return const Center(child: Text('Unknown state')); - }, - ), + return BlocProvider( + create: (context) => GarageDoorBloc(deviceId: deviceId)..add(GarageDoorInitialEvent(deviceId)), + child: BlocBuilder( + builder: (context, state) { + if (state is GarageDoorLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is GarageDoorLoadedState) { + return _buildControlView(context, state.status); + } else if (state is GarageDoorErrorState) { + return Center(child: Text('Error: ${state.message}')); + } + return const Center(child: Text('Unknown state')); + }, ), ); } @@ -50,6 +45,7 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout return GridView( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 50), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: isLarge || isExtraLarge ? 3 @@ -57,25 +53,29 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout ? 2 : 1, childAspectRatio: 1.5, + mainAxisExtent: 170, crossAxisSpacing: 12, mainAxisSpacing: 12, ), children: [ IconNameStatusContainer( - isFullIcon: true, - name: status.doorContactState ? 'Open' : 'Close', - icon: Assets.openCloseDoor, + isFullIcon: false, + name: status.switch1 ? 'Opened' : 'Closed', + icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor, onTap: () { context.read().add( - ToggleGarageDoorEvent(deviceId: status.uuid, isOpen: !status.isOpen, code: 'switch_1'), + ToggleGarageDoorEvent(deviceId: status.uuid, isOpen: !status.switch1, code: 'switch_1'), ); }, - status: status.doorContactState, - textColor: status.doorContactState ? ColorsManager.red : ColorsManager.blackColor, - paddingAmount: 8, + status: status.switch1, + textColor: ColorsManager.blackColor, + //paddingAmount: 6, ), - GestureDetector( + IconNameStatusContainer( onTap: () { + context.read().add( + FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'), + ); showDialog( context: context, builder: (ctx) => BlocProvider.value( @@ -83,43 +83,18 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout child: BuildGarageDoorScheduleView(status: status), )); }, - child: DeviceControlsContainer( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 60, - height: 60, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: ColorsManager.whiteColors, - ), - padding: const EdgeInsets.all(12), - child: ClipOval( - child: SvgPicture.asset( - Assets.scheduling, - fit: BoxFit.fill, - ), - ), - ), - const SizedBox(height: 8), - Text( - 'Scheduling', - textAlign: TextAlign.center, - style: context.textTheme.titleMedium!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor, - ), - ), - ], - ), - ), + name: 'Scheduling', + icon: Assets.acSchedule, + status: false, + textColor: ColorsManager.blackColor, + isFullIcon: false, + //paddingAmount: 15, ), ToggleWidget( label: '', labelWidget: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.end, children: [ IconButton( onPressed: () { @@ -168,9 +143,35 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout value: false, code: 'switch_1', deviceId: status.uuid, - icon: Assets.acSchedule, + icon: Assets.doorDelay, onChange: (value) {}, ), + IconNameStatusContainer( + isFullIcon: false, + name: 'Records', + icon: Assets.records, + onTap: () { + // context.read().add( + // (deviceId: status.uuid, isOpen: !status.isOpen, code: 'switch_1'), + // ); + }, + status: false, + textColor: ColorsManager.blackColor, + //paddingAmount: 6, + ), + IconNameStatusContainer( + isFullIcon: false, + name: 'Preferences', + icon: Assets.preferences, + onTap: () { + // context.read().add( + // (deviceId: status.uuid, isOpen: !status.isOpen, code: 'switch_1'), + // ); + }, + status: false, + textColor: ColorsManager.blackColor, + // paddingAmount: 6, + ), ], ); } diff --git a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart index 4bd54aa8..9bd75094 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart +++ b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart @@ -52,7 +52,7 @@ class ScheduleGarageTableWidget extends StatelessWidget { if (state is ScheduleGarageLoadingState) { return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator())); } - if (state is GarageDoorLoadedState && state.schedules?.isEmpty == true) { + if (state is GarageDoorLoadedState && state.status.schedules == null) { return _buildEmptyState(context); } else if (state is GarageDoorLoadedState) { return Container( @@ -110,9 +110,9 @@ class ScheduleGarageTableWidget extends StatelessWidget { border: TableBorder.all(color: ColorsManager.graysColor), defaultVerticalAlignment: TableCellVerticalAlignment.middle, children: [ - if (state.schedules != null) - for (int i = 0; i < state.schedules!.length; i++) - _buildScheduleRow(state.schedules![i], i, context, state), + if (state.status.schedules != null) + for (int i = 0; i < state.status.schedules!.length; i++) + _buildScheduleRow(state.status.schedules![i], i, context, state), ], ), ), diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart index f1307d5f..b30c3596 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart @@ -3,10 +3,10 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -class ScheduleModeButtons extends StatelessWidget { +class ScheduleGarageModeButtons extends StatelessWidget { final VoidCallback onSave; - const ScheduleModeButtons({ + const ScheduleGarageModeButtons({ super.key, required this.onSave, }); diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart index be11203f..8303f4b3 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart @@ -6,7 +6,7 @@ import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_doo import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_header.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_managment_ui.dart'; -import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_mode_selector.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_mode_buttons.dart'; class BuildGarageDoorScheduleView extends StatefulWidget { const BuildGarageDoorScheduleView({super.key, required this.status}); @@ -43,8 +43,6 @@ class _BuildScheduleViewState extends State { children: [ const ScheduleGarageHeader(), const SizedBox(height: 20), - ScheduleGarageDoorModeSelector(state: state), - const SizedBox(height: 20), ScheduleGarageManagementUI( state: state, onAddSchedule: () { @@ -52,20 +50,32 @@ class _BuildScheduleViewState extends State { schedule: null, index: null, isEdit: false); }, ), + const SizedBox(height: 20), + ScheduleGarageModeButtons( + onSave: () { + Navigator.pop(context); + }, + ), ], ); } - if (state is GarageDoorLoadingState) { - return const SizedBox( + if (state is ScheduleGarageLoadingState) { + return SizedBox( height: 200, child: Column( mainAxisSize: MainAxisSize.min, children: [ - ScheduleGarageHeader(), - SizedBox( + const ScheduleGarageHeader(), + const SizedBox( + height: 50, + ), + const Center(child: CircularProgressIndicator()), + const SizedBox( height: 20, ), - Center(child: CircularProgressIndicator()), + ScheduleGarageModeButtons( + onSave: () {}, + ), ], )); } diff --git a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart index 5785a799..fe03c74a 100644 --- a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart +++ b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart @@ -14,8 +14,7 @@ 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'; -class MainDoorSensorControlView extends StatelessWidget - with HelperResponsiveLayout { +class MainDoorSensorControlView extends StatelessWidget with HelperResponsiveLayout { const MainDoorSensorControlView({super.key, required this.device}); final AllDevicesModel device; @@ -23,12 +22,10 @@ class MainDoorSensorControlView extends StatelessWidget @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => MainDoorSensorBloc() - ..add(MainDoorSensorFetchDeviceEvent(device.uuid!)), + create: (context) => MainDoorSensorBloc()..add(MainDoorSensorFetchDeviceEvent(device.uuid!)), child: BlocBuilder( builder: (context, state) { - if (state is MainDoorSensorLoadingState || - state is MainDoorSensorReportsLoadingState) { + if (state is MainDoorSensorLoadingState || state is MainDoorSensorReportsLoadingState) { return const Center(child: CircularProgressIndicator()); } else if (state is MainDoorSensorDeviceStatusLoaded) { return _buildStatusControls(context, state.status); @@ -37,15 +34,12 @@ class MainDoorSensorControlView extends StatelessWidget report: state.deviceReport, onRowTap: (index) {}, onClose: () { - context - .read() - .add(MainDoorSensorFetchDeviceEvent(device.uuid!)); + context.read().add(MainDoorSensorFetchDeviceEvent(device.uuid!)); }, hideValueShowDescription: true, mainDoorSensor: true, ); - } else if (state is MainDoorSensorFailedState || - state is MainDoorSensorBatchFailedState) { + } else if (state is MainDoorSensorFailedState || state is MainDoorSensorBatchFailedState) { return const Center(child: Text('Error fetching status')); } else { return const Center(child: CircularProgressIndicator()); @@ -54,8 +48,7 @@ class MainDoorSensorControlView extends StatelessWidget )); } - Widget _buildStatusControls( - BuildContext context, MainDoorSensorStatusModel status) { + Widget _buildStatusControls(BuildContext context, MainDoorSensorStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -80,9 +73,7 @@ class MainDoorSensorControlView extends StatelessWidget icon: Assets.openCloseDoor, onTap: () {}, status: status.doorContactState, - textColor: status.doorContactState - ? ColorsManager.red - : ColorsManager.blackColor, + textColor: status.doorContactState ? ColorsManager.red : ColorsManager.blackColor, paddingAmount: 8, ), IconNameStatusContainer( @@ -90,9 +81,7 @@ class MainDoorSensorControlView extends StatelessWidget name: 'Open/Close\nRecord', icon: Assets.openCloseRecords, onTap: () { - final from = DateTime.now() - .subtract(const Duration(days: 30)) - .millisecondsSinceEpoch; + final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch; final to = DateTime.now().millisecondsSinceEpoch; context.read().add( MainDoorSensorReportsEvent( @@ -161,22 +150,19 @@ class IconNameStatusContainer extends StatelessWidget { ), ) else - Container( - width: 60, + ClipOval( + child: Container( height: 60, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: ColorsManager.whiteColors, + width: 60, + padding: EdgeInsets.all(paddingAmount ?? 8), + color: ColorsManager.whiteColors, + child: SvgPicture.asset( + icon, + width: 35, + height: 35, + fit: BoxFit.contain, ), - //margin: const EdgeInsets.symmetric(horizontal: 4), - padding: EdgeInsets.all(paddingAmount ?? 12), - child: ClipOval( - child: SvgPicture.asset( - icon, - fit: BoxFit.contain, - ), - ), - ), + )), const Spacer(), Padding( padding: const EdgeInsets.symmetric(horizontal: 6), diff --git a/lib/pages/device_managment/shared/device_control_dialog.dart b/lib/pages/device_managment/shared/device_control_dialog.dart index ba37203e..6a45ce18 100644 --- a/lib/pages/device_managment/shared/device_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_control_dialog.dart @@ -1,8 +1,5 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; - import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_controls_based_code.dart'; - import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/format_date_time.dart'; @@ -22,7 +19,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { ), child: SizedBox( width: 798, - // height: context.screenHeight * 0.7, + //height: context.screenHeight * 0.7, child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(20.0), @@ -113,13 +110,9 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { ), _buildInfoRow( 'Battery Level:', - device.batteryLevel != null - ? '${device.batteryLevel ?? 0}%' - : "-", + device.batteryLevel != null ? '${device.batteryLevel ?? 0}%' : "-", statusColor: device.batteryLevel != null - ? (device.batteryLevel! < 20 - ? ColorsManager.red - : ColorsManager.green) + ? (device.batteryLevel! < 20 ? ColorsManager.red : ColorsManager.green) : null, ), ], diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index ff1b3c15..b94c48c0 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -150,4 +150,9 @@ class Assets { //assets/icons/preferences.svg static const String preferences = 'assets/icons/preferences.svg'; + + static const String openedDoor = 'assets/icons/opened_door.svg'; + static const String closedDoor = 'assets/icons/closed_door.svg'; + static const String doorDelay = 'assets/icons/door_delay.svg'; + static const String records = 'assets/icons/records.svg'; } From 9733295dcac81d1e3aad9fbe6c53e48858f66e74 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 7 Oct 2024 00:45:34 +0300 Subject: [PATCH 53/65] push garage door delay --- .../garage_door/bloc/garage_door_bloc.dart | 92 +++++++++++-------- .../garage_door/bloc/garage_door_event.dart | 8 +- .../view/garage_door_control_view.dart | 29 ++++-- 3 files changed, 78 insertions(+), 51 deletions(-) diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index 10ccbca4..bc67b081 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -19,7 +19,7 @@ class GarageDoorBloc extends Bloc { GarageDoorBloc({required this.deviceId}) : super(GarageDoorInitialState()) { on(_fetchGarageDoorStatus); - on(_toggleGarageDoor); + on(_garageDoorControlEvent); on(_addSchedule); on(_updateSchedule); on(_deleteSchedule); @@ -45,23 +45,6 @@ class GarageDoorBloc extends Bloc { } } - void _toggleGarageDoor(ToggleGarageDoorEvent event, Emitter emit) async { - final oldValue = deviceStatus.switch1; - _updateLocalValue('switch_1', event.isOpen); - emit(GarageDoorLoadedState(status: deviceStatus)); - final success = await _runDeBouncer( - deviceId: event.deviceId, - code: 'switch_1', - value: event.isOpen, - oldValue: oldValue, - emit: emit, - isBatch: false, - ); - if (!success) { - _revertValue('switch_1', oldValue, emit); - } - } - Future _addSchedule(AddGarageDoorScheduleEvent event, Emitter emit) async { try { ScheduleEntry newSchedule = ScheduleEntry( @@ -146,26 +129,6 @@ class GarageDoorBloc extends Bloc { } } - void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter emit) async { - try { - deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10)); - emit(GarageDoorLoadedState(status: deviceStatus)); - } catch (e) { - emit(GarageDoorErrorState(message: e.toString())); - } - } - - void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter emit) async { - try { - if (deviceStatus.delay.inMinutes > 10) { - deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10)); - } - emit(GarageDoorLoadedState(status: deviceStatus)); - } catch (e) { - emit(GarageDoorErrorState(message: e.toString())); - } - } - Future _updateSelectedTime(UpdateSelectedTimeEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { @@ -244,6 +207,49 @@ class GarageDoorBloc extends Bloc { } } + void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter emit) async { + // if (deviceStatus.countdown1 != 0) { + try { + deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10)); + emit(GarageDoorLoadedState(status: deviceStatus)); + add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1')); + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + // } + } + + void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter emit) async { + // if (deviceStatus.countdown1 != 0) { + try { + if (deviceStatus.delay.inMinutes > 10) { + deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10)); + } + emit(GarageDoorLoadedState(status: deviceStatus)); + add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1')); + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + //} + } + + void _garageDoorControlEvent(GarageDoorControlEvent event, Emitter emit) async { + final oldValue = event.code == 'countdown_1' ? deviceStatus.countdown1 : deviceStatus.switch1; + _updateLocalValue(event.code, event.value); + emit(GarageDoorLoadedState(status: deviceStatus)); + final success = await _runDeBouncer( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: false, + ); + if (!success) { + _revertValue(event.code, oldValue, emit); + } + } + void _revertValue(String code, dynamic oldValue, Emitter emit) { switch (code) { case 'switch_1': @@ -251,6 +257,11 @@ class GarageDoorBloc extends Bloc { deviceStatus = deviceStatus.copyWith(switch1: oldValue); } break; + case 'countdown_1': + if (oldValue is int) { + deviceStatus = deviceStatus.copyWith(countdown1: oldValue, delay: Duration(seconds: oldValue)); + } + break; // Add other cases if needed default: break; @@ -268,6 +279,11 @@ class GarageDoorBloc extends Bloc { deviceStatus = deviceStatus.copyWith(switch1: value); } break; + case 'countdown_1': + if (value is int) { + deviceStatus = deviceStatus.copyWith(countdown1: value, delay: Duration(seconds: value)); + } + break; // Add other cases if needed default: break; diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart index 754ae140..ee07713a 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart @@ -19,15 +19,15 @@ class GarageDoorInitialEvent extends GarageDoorEvent { List get props => [deviceId]; } -class ToggleGarageDoorEvent extends GarageDoorEvent { +class GarageDoorControlEvent extends GarageDoorEvent { final String deviceId; - final bool isOpen; + final dynamic value; final String code; - const ToggleGarageDoorEvent({required this.deviceId, required this.isOpen, required this.code}); + const GarageDoorControlEvent({required this.deviceId, required this.value, required this.code}); @override - List get props => [deviceId, isOpen]; + List get props => [deviceId, value]; } class AddGarageDoorScheduleEvent extends GarageDoorEvent { diff --git a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart index 8e6201f7..41e6baee 100644 --- a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart +++ b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart @@ -64,7 +64,7 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor, onTap: () { context.read().add( - ToggleGarageDoorEvent(deviceId: status.uuid, isOpen: !status.switch1, code: 'switch_1'), + GarageDoorControlEvent(deviceId: status.uuid, value: !status.switch1, code: 'switch_1'), ); }, status: status.switch1, @@ -94,20 +94,23 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout label: '', labelWidget: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, children: [ IconButton( onPressed: () { - context.read().add(DecreaseGarageDoorDelayEvent(deviceId: deviceId)); + // if (status.countdown1 != 0) { + context.read().add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid)); + // } }, icon: const Icon( Icons.remove, size: 28, color: ColorsManager.greyColor, ), + padding: EdgeInsets.zero, ), Text( - '${status.delay.inHours.toString().padLeft(2, '0')}', + status.delay.inHours.toString().padLeft(2, '0'), style: context.textTheme.titleLarge!.copyWith( color: ColorsManager.dialogBlueTitle, fontWeight: FontWeight.bold, @@ -118,7 +121,7 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), ), Text( - '${(status.delay.inMinutes % 60).toString().padLeft(2, '0')}', + (status.delay.inMinutes % 60).toString().padLeft(2, '0'), style: context.textTheme.titleLarge!.copyWith( color: ColorsManager.dialogBlueTitle, fontWeight: FontWeight.bold, @@ -130,21 +133,29 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout ), IconButton( onPressed: () { - context.read().add(IncreaseGarageDoorDelayEvent(deviceId: deviceId)); + // if (status.countdown1 != 0) { + context.read().add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid)); + // } }, icon: const Icon( Icons.add, size: 28, color: ColorsManager.greyColor, ), + padding: EdgeInsets.zero, ), ], ), - value: false, - code: 'switch_1', + value: status.countdown1 != 0 ? true : false, + code: 'countdown_1', deviceId: status.uuid, icon: Assets.doorDelay, - onChange: (value) {}, + onChange: (value) { + context.read().add( + GarageDoorControlEvent( + deviceId: status.uuid, value: value ? status.delay.inSeconds : 0, code: 'countdown_1'), + ); + }, ), IconNameStatusContainer( isFullIcon: false, From 00277742d0750444f9c8bfab65eb0965ccb8d1af Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 7 Oct 2024 01:12:35 +0300 Subject: [PATCH 54/65] push garage door records --- .../garage_door/bloc/garage_door_bloc.dart | 10 ++++- .../garage_door/bloc/garage_door_event.dart | 7 +++- .../view/garage_door_control_view.dart | 21 +++++++---- .../shared/table/report_table.dart | 37 ++++++++++--------- 4 files changed, 47 insertions(+), 28 deletions(-) diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index bc67b081..f640a50d 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -32,6 +32,7 @@ class GarageDoorBloc extends Bloc { on(_updateSelectedDay); on(_updateFunctionOn); on(_initializeAddSchedule); + on(_backToGridView); } void _fetchGarageDoorStatus(GarageDoorInitialEvent event, Emitter emit) async { @@ -167,13 +168,20 @@ class GarageDoorBloc extends Bloc { Future _fetchRecords(FetchGarageDoorRecordsEvent event, Emitter emit) async { emit(GarageDoorReportsLoadingState()); try { - final DeviceReport records = await DevicesManagementApi.getDeviceReports(event.deviceId, 'switch_1'); + final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch; + final to = DateTime.now().millisecondsSinceEpoch; + final DeviceReport records = + await DevicesManagementApi.getDeviceReportsByDate(event.deviceId, 'switch_1', from.toString(), to.toString()); emit(GarageDoorReportsState(deviceReport: records)); } catch (e) { emit(GarageDoorReportsFailedState(error: e.toString())); } } + void _backToGridView(BackToGarageDoorGridViewEvent event, Emitter emit) { + emit(GarageDoorLoadedState(status: deviceStatus)); + } + void _handleUpdate(GarageDoorUpdatedEvent event, Emitter emit) { emit(GarageDoorLoadedState(status: deviceStatus)); } diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart index ee07713a..20758077 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart @@ -102,13 +102,16 @@ class DecreaseGarageDoorDelayEvent extends GarageDoorEvent { class FetchGarageDoorRecordsEvent extends GarageDoorEvent { final String deviceId; + final String code; - const FetchGarageDoorRecordsEvent({required this.deviceId}); + const FetchGarageDoorRecordsEvent({required this.deviceId, required this.code}); @override - List get props => [deviceId]; + List get props => [deviceId, code]; } +class BackToGarageDoorGridViewEvent extends GarageDoorEvent {} + class GarageDoorUpdatedEvent extends GarageDoorEvent {} class UpdateSelectedTimeEvent extends GarageDoorEvent { diff --git a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart index 41e6baee..1ad3c756 100644 --- a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart +++ b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_ import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_view.dart'; +import 'package:syncrow_web/pages/device_managment/shared/table/report_table.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/constants/assets.dart'; @@ -26,6 +27,16 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout builder: (context, state) { if (state is GarageDoorLoadingState) { return const Center(child: CircularProgressIndicator()); + } else if (state is GarageDoorReportsState) { + return ReportsTable( + report: state.deviceReport, + hideValueShowDescription: true, + garageDoorSensor: true, + onRowTap: (index) {}, + onClose: () { + context.read().add(BackToGarageDoorGridViewEvent()); + }, + ); } else if (state is GarageDoorLoadedState) { return _buildControlView(context, state.status); } else if (state is GarageDoorErrorState) { @@ -162,9 +173,7 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout name: 'Records', icon: Assets.records, onTap: () { - // context.read().add( - // (deviceId: status.uuid, isOpen: !status.isOpen, code: 'switch_1'), - // ); + context.read().add(FetchGarageDoorRecordsEvent(code: 'switch_1', deviceId: status.uuid)); }, status: false, textColor: ColorsManager.blackColor, @@ -174,11 +183,7 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout isFullIcon: false, name: 'Preferences', icon: Assets.preferences, - onTap: () { - // context.read().add( - // (deviceId: status.uuid, isOpen: !status.isOpen, code: 'switch_1'), - // ); - }, + onTap: () {}, status: false, textColor: ColorsManager.blackColor, // paddingAmount: 6, diff --git a/lib/pages/device_managment/shared/table/report_table.dart b/lib/pages/device_managment/shared/table/report_table.dart index 7dda10e1..527ae783 100644 --- a/lib/pages/device_managment/shared/table/report_table.dart +++ b/lib/pages/device_managment/shared/table/report_table.dart @@ -13,6 +13,7 @@ class ReportsTable extends StatelessWidget { final VoidCallback onClose; bool? hideValueShowDescription; bool? mainDoorSensor; + bool? garageDoorSensor; ReportsTable({ super.key, @@ -23,6 +24,7 @@ class ReportsTable extends StatelessWidget { this.thirdColumnDescription, this.hideValueShowDescription, this.mainDoorSensor, + this.garageDoorSensor, }); @override @@ -53,30 +55,31 @@ class ReportsTable extends StatelessWidget { DeviceEvent data = entry.value; // Parse eventTime into Date and Time - DateTime eventDateTime = - DateTime.fromMillisecondsSinceEpoch(data.eventTime!); + DateTime eventDateTime = DateTime.fromMillisecondsSinceEpoch(data.eventTime!); String date = DateFormat('dd/MM/yyyy').format(eventDateTime); String time = DateFormat('HH:mm').format(eventDateTime); + String value; + if (hideValueShowDescription == true) { + if (mainDoorSensor != null && mainDoorSensor == true) { + value = data.value == 'true' ? 'Open' : 'Close'; + } else if (garageDoorSensor != null && garageDoorSensor == true) { + value = data.value == 'true' ? 'Opened' : 'Closed'; + } else { + value = '${data.value!} ${thirdColumnDescription ?? ''}'; + } + } else { + value = '${data.value!} ${thirdColumnDescription ?? ''}'; + } + return TableRow( children: [ TableCellWidget(value: date), TableCellWidget(value: time), - hideValueShowDescription == true - ? TableCellWidget( - value: (mainDoorSensor != null && - mainDoorSensor == true) - ? data.value == 'true' - ? 'Open' - : 'Close' - : thirdColumnDescription ?? '', - onTap: () => onRowTap(index), - ) - : TableCellWidget( - value: - '${data.value!} ${thirdColumnDescription ?? ''}', - onTap: () => onRowTap(index), - ), + TableCellWidget( + value: value, + onTap: () => onRowTap(index), + ), ], ); }), From ebde81b64de6067b1b49581088ebdf431bb4fcb6 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 7 Oct 2024 07:43:07 +0300 Subject: [PATCH 55/65] working on preferences --- .../helper/garage_door_helper.dart | 129 ++++++++++++++++-- .../view/garage_door_control_view.dart | 54 +++++--- .../opening_clsoing_time_dialog_body.dart | 13 ++ .../widgets/schedule__garage_table.dart | 48 +++++-- .../widgets/schedule_garage_view.dart | 8 +- .../widgets/time_out_alarm_dialog_body.dart | 13 ++ .../shared/toggle_widget.dart | 2 + lib/utils/extension/build_context_x.dart | 112 +++++++++++++++ 8 files changed, 330 insertions(+), 49 deletions(-) create mode 100644 lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart create mode 100644 lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart diff --git a/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart b/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart index e939ad9e..55957b53 100644 --- a/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart +++ b/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart @@ -4,11 +4,15 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -class ScheduleGarageDoorDialogHelper { +class GarageDoorDialogHelper { static void showAddGarageDoorScheduleDialog(BuildContext context, {ScheduleModel? schedule, int? index, bool? isEdit}) { final bloc = context.read(); @@ -77,7 +81,8 @@ class ScheduleGarageDoorDialogHelper { : () async { TimeOfDay? time = await showTimePicker( context: context, - initialTime: state.selectedTime ?? TimeOfDay.now(), + initialTime: + state.selectedTime ?? TimeOfDay.now(), builder: (context, child) { return Theme( data: Theme.of(context).copyWith( @@ -97,7 +102,9 @@ class ScheduleGarageDoorDialogHelper { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - state.selectedTime == null ? 'Time' : state.selectedTime!.format(context), + state.selectedTime == null + ? 'Time' + : state.selectedTime!.format(context), style: context.textTheme.bodySmall!.copyWith( color: ColorsManager.grayColor, ), @@ -112,7 +119,8 @@ class ScheduleGarageDoorDialogHelper { ), ), const SizedBox(height: 16), - _buildDayCheckboxes(context, state.selectedDays, isEdit: isEdit), + _buildDayCheckboxes(context, state.selectedDays, + isEdit: isEdit), const SizedBox(height: 16), _buildFunctionSwitch(context, state.functionOn, isEdit), ], @@ -191,7 +199,9 @@ class ScheduleGarageDoorDialogHelper { return daysBoolean; } - static Widget _buildDayCheckboxes(BuildContext context, List selectedDays, {bool? isEdit}) { + static Widget _buildDayCheckboxes( + BuildContext context, List selectedDays, + {bool? isEdit}) { final dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; return Row( @@ -203,7 +213,9 @@ class ScheduleGarageDoorDialogHelper { onChanged: isEdit == true ? null : (bool? value) { - context.read().add(UpdateSelectedDayEvent(index, value!)); + context + .read() + .add(UpdateSelectedDayEvent(index, value!)); }, ), Text(dayLabels[index]), @@ -213,12 +225,14 @@ class ScheduleGarageDoorDialogHelper { ); } - static Widget _buildFunctionSwitch(BuildContext context, bool isOn, bool? isEdit) { + static Widget _buildFunctionSwitch( + BuildContext context, bool isOn, bool? isEdit) { return Row( children: [ Text( 'Function:', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.grayColor), + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.grayColor), ), const SizedBox(width: 10), Radio( @@ -228,7 +242,9 @@ class ScheduleGarageDoorDialogHelper { if (isEdit == true) { return; } else { - context.read().add(const UpdateFunctionOnEvent(functionOn: true)); + context + .read() + .add(const UpdateFunctionOnEvent(functionOn: true)); } }, ), @@ -241,7 +257,9 @@ class ScheduleGarageDoorDialogHelper { if (isEdit == true) { return; } else { - context.read().add(const UpdateFunctionOnEvent(functionOn: false)); + context + .read() + .add(const UpdateFunctionOnEvent(functionOn: false)); } }, ), @@ -249,4 +267,95 @@ class ScheduleGarageDoorDialogHelper { ], ); } + + static void showPreferencesDialog(BuildContext context) { + final bloc = context.read(); + + showDialog( + context: context, + builder: (ctx) { + return BlocProvider.value( + value: bloc, + child: BlocBuilder( + builder: (context, state) { + if (state is GarageDoorLoadedState) { + 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( + 'Preferences', + style: context.textTheme.titleLarge!.copyWith( + color: ColorsManager.dialogBlueTitle, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(), + ], + ), + const SizedBox(height: 24), + Row( + children: [ + const SizedBox(width: 24), + SizedBox( + width: 190, + height: 150, + child: GestureDetector( + onTap: () { + context.customAlertDialog( + alertBody: TimeOutAlarmDialogBody(), + title: 'Time Out Alarm', + onConfirm: () {}); + }, + child: ToggleWidget( + icon: "-1", + value: bloc.deviceStatus.countdownAlarm > 0, + code: 'countdown_alarm', + deviceId: bloc.deviceId, + label: 'Alarm when door is open', + onChange: (value) {}), + ), + ), + const SizedBox( + width: 20, + ), + SizedBox( + width: 190, + height: 150, + child: GestureDetector( + onTap: () { + context.customAlertDialog( + alertBody: OpeningClosingTimeDialogBody(), + title: 'Opening and Closing Time', + onConfirm: () {}); + }, + child: PresenceDisplayValue( + value: bloc.deviceStatus.trTimeCon.toString(), + postfix: 'sec', + description: 'Opening & Closing Time', + ), + ), + ), + const SizedBox(width: 24), + ], + ) + ], + ), + ); + } + return const SizedBox(); + }, + ), + ); + }, + ); + } } diff --git a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart index 1ad3c756..e3c44405 100644 --- a/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart +++ b/lib/pages/device_managment/garage_door/view/garage_door_control_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/helper/garage_door_helper.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/widgets/schedule_garage_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart'; @@ -14,7 +15,8 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la import '../../main_door_sensor/view/main_door_control_view.dart'; -class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout { +class GarageDoorControlView extends StatelessWidget + with HelperResponsiveLayout { final String deviceId; const GarageDoorControlView({required this.deviceId, super.key}); @@ -22,7 +24,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => GarageDoorBloc(deviceId: deviceId)..add(GarageDoorInitialEvent(deviceId)), + create: (context) => GarageDoorBloc(deviceId: deviceId) + ..add(GarageDoorInitialEvent(deviceId)), child: BlocBuilder( builder: (context, state) { if (state is GarageDoorLoadingState) { @@ -34,7 +37,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout garageDoorSensor: true, onRowTap: (index) {}, onClose: () { - context.read().add(BackToGarageDoorGridViewEvent()); + context + .read() + .add(BackToGarageDoorGridViewEvent()); }, ); } else if (state is GarageDoorLoadedState) { @@ -64,7 +69,7 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout ? 2 : 1, childAspectRatio: 1.5, - mainAxisExtent: 170, + mainAxisExtent: 140, crossAxisSpacing: 12, mainAxisSpacing: 12, ), @@ -75,17 +80,20 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor, onTap: () { context.read().add( - GarageDoorControlEvent(deviceId: status.uuid, value: !status.switch1, code: 'switch_1'), + GarageDoorControlEvent( + deviceId: status.uuid, + value: !status.switch1, + code: 'switch_1'), ); }, status: status.switch1, textColor: ColorsManager.blackColor, - //paddingAmount: 6, ), IconNameStatusContainer( onTap: () { context.read().add( - FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1'), + FetchGarageDoorSchedulesEvent( + deviceId: deviceId, category: 'switch_1'), ); showDialog( context: context, @@ -99,7 +107,6 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout status: false, textColor: ColorsManager.blackColor, isFullIcon: false, - //paddingAmount: 15, ), ToggleWidget( label: '', @@ -109,9 +116,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout children: [ IconButton( onPressed: () { - // if (status.countdown1 != 0) { - context.read().add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid)); - // } + context + .read() + .add(DecreaseGarageDoorDelayEvent(deviceId: status.uuid)); }, icon: const Icon( Icons.remove, @@ -129,7 +136,8 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout ), Text( 'h', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blackColor), ), Text( (status.delay.inMinutes % 60).toString().padLeft(2, '0'), @@ -140,13 +148,14 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout ), Text( 'm', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blackColor), ), IconButton( onPressed: () { - // if (status.countdown1 != 0) { - context.read().add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid)); - // } + context + .read() + .add(IncreaseGarageDoorDelayEvent(deviceId: status.uuid)); }, icon: const Icon( Icons.add, @@ -164,7 +173,9 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout onChange: (value) { context.read().add( GarageDoorControlEvent( - deviceId: status.uuid, value: value ? status.delay.inSeconds : 0, code: 'countdown_1'), + deviceId: status.uuid, + value: value ? status.delay.inSeconds : 0, + code: 'countdown_1'), ); }, ), @@ -173,20 +184,21 @@ class GarageDoorControlView extends StatelessWidget with HelperResponsiveLayout name: 'Records', icon: Assets.records, onTap: () { - context.read().add(FetchGarageDoorRecordsEvent(code: 'switch_1', deviceId: status.uuid)); + context.read().add(FetchGarageDoorRecordsEvent( + code: 'switch_1', deviceId: status.uuid)); }, status: false, textColor: ColorsManager.blackColor, - //paddingAmount: 6, ), IconNameStatusContainer( isFullIcon: false, name: 'Preferences', icon: Assets.preferences, - onTap: () {}, + onTap: () { + GarageDoorDialogHelper.showPreferencesDialog(context); + }, status: false, textColor: ColorsManager.blackColor, - // paddingAmount: 6, ), ], ); diff --git a/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart b/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart new file mode 100644 index 00000000..ea76d901 --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class OpeningClosingTimeDialogBody extends StatelessWidget { + const OpeningClosingTimeDialogBody({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: 350, + child: Text('asdasd'), + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart index 9bd75094..48ebbcad 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart +++ b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart @@ -26,7 +26,8 @@ class ScheduleGarageTableWidget extends StatelessWidget { Table( border: TableBorder.all( color: ColorsManager.graysColor, - borderRadius: const BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), topRight: Radius.circular(20)), ), children: [ TableRow( @@ -50,17 +51,21 @@ class ScheduleGarageTableWidget extends StatelessWidget { BlocBuilder( builder: (context, state) { if (state is ScheduleGarageLoadingState) { - return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator())); + return const SizedBox( + height: 200, + child: Center(child: CircularProgressIndicator())); } - if (state is GarageDoorLoadedState && state.status.schedules == null) { + if (state is GarageDoorLoadedState && + state.status.schedules == null) { return _buildEmptyState(context); } else if (state is GarageDoorLoadedState) { return Container( height: 200, decoration: BoxDecoration( border: Border.all(color: ColorsManager.graysColor), - borderRadius: - const BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(20), + bottomRight: Radius.circular(20)), ), child: _buildTableBody(state, context)); } @@ -78,7 +83,8 @@ class ScheduleGarageTableWidget extends StatelessWidget { height: 200, decoration: BoxDecoration( border: Border.all(color: ColorsManager.graysColor), - borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), ), child: Center( child: Column( @@ -112,7 +118,8 @@ class ScheduleGarageTableWidget extends StatelessWidget { children: [ if (state.status.schedules != null) for (int i = 0; i < state.status.schedules!.length; i++) - _buildScheduleRow(state.status.schedules![i], i, context, state), + _buildScheduleRow( + state.status.schedules![i], i, context, state), ], ), ), @@ -134,7 +141,8 @@ class ScheduleGarageTableWidget extends StatelessWidget { ); } - TableRow _buildScheduleRow(ScheduleModel schedule, int index, BuildContext context, GarageDoorLoadedState state) { + TableRow _buildScheduleRow(ScheduleModel schedule, int index, + BuildContext context, GarageDoorLoadedState state) { return TableRow( children: [ Center( @@ -152,7 +160,8 @@ class ScheduleGarageTableWidget extends StatelessWidget { width: 24, height: 24, child: schedule.enable - ? const Icon(Icons.radio_button_checked, color: ColorsManager.blueColor) + ? const Icon(Icons.radio_button_checked, + color: ColorsManager.blueColor) : const Icon( Icons.radio_button_unchecked, color: ColorsManager.grayColor, @@ -160,7 +169,9 @@ class ScheduleGarageTableWidget extends StatelessWidget { ), ), ), - Center(child: Text(_getSelectedDays(ScheduleModel.parseSelectedDays(schedule.days)))), + Center( + child: Text(_getSelectedDays( + ScheduleModel.parseSelectedDays(schedule.days)))), Center(child: Text(formatIsoStringToTime(schedule.time, context))), Center(child: Text(schedule.function.value ? 'On' : 'Off')), Center( @@ -170,18 +181,24 @@ class ScheduleGarageTableWidget extends StatelessWidget { TextButton( style: TextButton.styleFrom(padding: EdgeInsets.zero), onPressed: () { - ScheduleGarageDoorDialogHelper.showAddGarageDoorScheduleDialog(context, - schedule: schedule, index: index, isEdit: true); + GarageDoorDialogHelper.showAddGarageDoorScheduleDialog( + context, + schedule: schedule, + index: index, + isEdit: true); }, child: Text( 'Edit', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blueColor), + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blueColor), ), ), TextButton( style: TextButton.styleFrom(padding: EdgeInsets.zero), onPressed: () { - context.read().add(DeleteGarageDoorScheduleEvent( + context + .read() + .add(DeleteGarageDoorScheduleEvent( index: index, scheduleId: schedule.scheduleId, deviceId: state.status.uuid, @@ -189,7 +206,8 @@ class ScheduleGarageTableWidget extends StatelessWidget { }, child: Text( 'Delete', - style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blueColor), + style: context.textTheme.bodySmall! + .copyWith(color: ColorsManager.blueColor), ), ), ], diff --git a/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart b/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart index 8303f4b3..107c8e0a 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart +++ b/lib/pages/device_managment/garage_door/widgets/schedule_garage_view.dart @@ -34,7 +34,8 @@ class _BuildScheduleViewState extends State { width: 700, child: SingleChildScrollView( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 40.0, vertical: 20), + padding: + const EdgeInsets.symmetric(horizontal: 40.0, vertical: 20), child: BlocBuilder( builder: (context, state) { if (state is GarageDoorLoadedState) { @@ -46,8 +47,9 @@ class _BuildScheduleViewState extends State { ScheduleGarageManagementUI( state: state, onAddSchedule: () { - ScheduleGarageDoorDialogHelper.showAddGarageDoorScheduleDialog(context, - schedule: null, index: null, isEdit: false); + GarageDoorDialogHelper + .showAddGarageDoorScheduleDialog(context, + schedule: null, index: null, isEdit: false); }, ), const SizedBox(height: 20), diff --git a/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart b/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart new file mode 100644 index 00000000..948f8190 --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class TimeOutAlarmDialogBody extends StatelessWidget { + const TimeOutAlarmDialogBody({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: 350, + child: Text('asdasd'), + ); + } +} diff --git a/lib/pages/device_managment/shared/toggle_widget.dart b/lib/pages/device_managment/shared/toggle_widget.dart index ee76d442..942c1b32 100644 --- a/lib/pages/device_managment/shared/toggle_widget.dart +++ b/lib/pages/device_managment/shared/toggle_widget.dart @@ -13,6 +13,7 @@ class ToggleWidget extends StatelessWidget { final Widget? labelWidget; final Function(dynamic value) onChange; final bool showToggle; + final bool showIcon; const ToggleWidget({ super.key, @@ -24,6 +25,7 @@ class ToggleWidget extends StatelessWidget { this.icon, this.labelWidget, this.showToggle = true, + this.showIcon = true, }); @override diff --git a/lib/utils/extension/build_context_x.dart b/lib/utils/extension/build_context_x.dart index 50bc5972..dbdbb347 100644 --- a/lib/utils/extension/build_context_x.dart +++ b/lib/utils/extension/build_context_x.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; extension BuildContextExt on BuildContext { ThemeData get theme => Theme.of(this); @@ -14,4 +15,115 @@ extension BuildContextExt on BuildContext { double get screenHeight => MediaQuery.of(this).size.height; double get textScale => MediaQuery.textScalerOf(this).scale(1); + + void customAlertDialog({ + required Widget alertBody, + required String title, + required VoidCallback onConfirm, + VoidCallback? onDismiss, + bool? hideConfirmButton, + final double? dialogWidth, + }) { + showDialog( + context: this, + builder: (BuildContext context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: Container( + width: dialogWidth ?? 360, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.only(top: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + /// header widget + Text( + title, + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.primaryColorWithOpacity, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, + horizontal: 50, + ), + child: Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + ), + + /// custom body content + Flexible(child: SingleChildScrollView(child: alertBody)), + + /// Footer buttons + Container( + height: 1, + width: double.infinity, + color: ColorsManager.greyColor, + ), + hideConfirmButton != true + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + GestureDetector( + onTap: onDismiss ?? + () { + Navigator.pop(context); + }, + child: Center( + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium! + .copyWith(color: ColorsManager.greyColor), + ), + ), + ), + Container( + height: 50, + width: 1, + color: ColorsManager.greyColor, + ), + GestureDetector( + onTap: onConfirm, + child: Center( + child: Text( + 'Confirm', + style: context.textTheme.bodyMedium!.copyWith( + color: + ColorsManager.primaryColorWithOpacity), + ), + ), + ), + ], + ) + : Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: GestureDetector( + onTap: onDismiss ?? + () { + Navigator.pop(context); + }, + child: Center( + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium! + .copyWith(color: ColorsManager.greyColor), + ), + ), + ), + ), + ], + ), + ), + ); + }, + ); + } } From 3748fc1419e7d967cc4f03efcefce08cc9d7e530 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 7 Oct 2024 09:31:33 +0300 Subject: [PATCH 56/65] push garage door preferences --- .../garage_door/bloc/garage_door_bloc.dart | 149 +++++++++++++----- .../garage_door/bloc/garage_door_event.dart | 18 ++- .../helper/garage_door_helper.dart | 60 ++++++- .../opening_clsoing_time_dialog_body.dart | 48 +++++- .../garage_door/widgets/seconds_picker.dart | 52 ++++++ .../widgets/time_out_alarm_dialog_body.dart | 44 +++++- 6 files changed, 317 insertions(+), 54 deletions(-) create mode 100644 lib/pages/device_managment/garage_door/widgets/seconds_picker.dart diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index f640a50d..04af1e39 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -33,12 +33,16 @@ class GarageDoorBloc extends Bloc { on(_updateFunctionOn); on(_initializeAddSchedule); on(_backToGridView); + on(_onUpdateCountdownAlarm); + on(_onUpdateTrTimeCon); } - void _fetchGarageDoorStatus(GarageDoorInitialEvent event, Emitter emit) async { + void _fetchGarageDoorStatus( + GarageDoorInitialEvent event, Emitter emit) async { emit(GarageDoorLoadingState()); try { - var response = await DevicesManagementApi().getDeviceStatus(event.deviceId); + var response = + await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status); emit(GarageDoorLoadedState(status: deviceStatus)); } catch (e) { @@ -46,7 +50,8 @@ class GarageDoorBloc extends Bloc { } } - Future _addSchedule(AddGarageDoorScheduleEvent event, Emitter emit) async { + Future _addSchedule( + AddGarageDoorScheduleEvent event, Emitter emit) async { try { ScheduleEntry newSchedule = ScheduleEntry( category: event.category, @@ -54,9 +59,11 @@ class GarageDoorBloc extends Bloc { function: Status(code: 'switch_1', value: event.functionOn), days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), ); - bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); + bool success = + await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); if (success) { - add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1')); + add(FetchGarageDoorSchedulesEvent( + deviceId: deviceId, category: 'switch_1')); } else { emit(GarageDoorLoadedState(status: deviceStatus)); } @@ -65,7 +72,29 @@ class GarageDoorBloc extends Bloc { } } - Future _updateSchedule(UpdateGarageDoorScheduleEvent event, Emitter emit) async { + void _onUpdateCountdownAlarm( + UpdateCountdownAlarmEvent event, Emitter emit) { + final currentState = state; + if (currentState is GarageDoorLoadedState) { + emit(currentState.copyWith( + status: + currentState.status.copyWith(countdownAlarm: event.countdownAlarm), + )); + } + } + + void _onUpdateTrTimeCon( + UpdateTrTimeConEvent event, Emitter emit) { + final currentState = state; + if (currentState is GarageDoorLoadedState) { + emit(currentState.copyWith( + status: currentState.status.copyWith(trTimeCon: event.trTimeCon), + )); + } + } + + Future _updateSchedule(UpdateGarageDoorScheduleEvent event, + Emitter emit) async { try { final updatedSchedules = deviceStatus.schedules?.map((schedule) { if (schedule.scheduleId == event.scheduleId) { @@ -92,12 +121,15 @@ class GarageDoorBloc extends Bloc { } } - Future _deleteSchedule(DeleteGarageDoorScheduleEvent event, Emitter emit) async { + Future _deleteSchedule(DeleteGarageDoorScheduleEvent event, + Emitter emit) async { try { - bool success = await DevicesManagementApi().deleteScheduleRecord(deviceStatus.uuid, event.scheduleId); + bool success = await DevicesManagementApi() + .deleteScheduleRecord(deviceStatus.uuid, event.scheduleId); if (success) { - final updatedSchedules = - deviceStatus.schedules?.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); + final updatedSchedules = deviceStatus.schedules + ?.where((schedule) => schedule.scheduleId != event.scheduleId) + .toList(); deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules); emit(GarageDoorLoadedState(status: deviceStatus)); } else { @@ -108,11 +140,12 @@ class GarageDoorBloc extends Bloc { } } - Future _fetchSchedules(FetchGarageDoorSchedulesEvent event, Emitter emit) async { + Future _fetchSchedules(FetchGarageDoorSchedulesEvent event, + Emitter emit) async { emit(ScheduleGarageLoadingState()); try { - List schedules = - await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category); + List schedules = await DevicesManagementApi() + .getDeviceSchedules(deviceStatus.uuid, event.category); deviceStatus = deviceStatus.copyWith(schedules: schedules); emit( GarageDoorLoadedState( @@ -130,30 +163,37 @@ class GarageDoorBloc extends Bloc { } } - Future _updateSelectedTime(UpdateSelectedTimeEvent event, Emitter emit) async { + Future _updateSelectedTime( + UpdateSelectedTimeEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { emit(currentState.copyWith(selectedTime: event.selectedTime)); } } - Future _updateSelectedDay(UpdateSelectedDayEvent event, Emitter emit) async { + Future _updateSelectedDay( + UpdateSelectedDayEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { List updatedDays = List.from(currentState.selectedDays); updatedDays[event.dayIndex] = event.isSelected; - emit(currentState.copyWith(selectedDays: updatedDays, selectedTime: currentState.selectedTime)); + emit(currentState.copyWith( + selectedDays: updatedDays, selectedTime: currentState.selectedTime)); } } - Future _updateFunctionOn(UpdateFunctionOnEvent event, Emitter emit) async { + Future _updateFunctionOn( + UpdateFunctionOnEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { - emit(currentState.copyWith(functionOn: event.functionOn, selectedTime: currentState.selectedTime)); + emit(currentState.copyWith( + functionOn: event.functionOn, + selectedTime: currentState.selectedTime)); } } - Future _initializeAddSchedule(InitializeAddScheduleEvent event, Emitter emit) async { + Future _initializeAddSchedule( + InitializeAddScheduleEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { emit(currentState.copyWith( @@ -165,24 +205,30 @@ class GarageDoorBloc extends Bloc { } } - Future _fetchRecords(FetchGarageDoorRecordsEvent event, Emitter emit) async { + Future _fetchRecords( + FetchGarageDoorRecordsEvent event, Emitter emit) async { emit(GarageDoorReportsLoadingState()); try { - final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch; + final from = DateTime.now() + .subtract(const Duration(days: 30)) + .millisecondsSinceEpoch; final to = DateTime.now().millisecondsSinceEpoch; final DeviceReport records = - await DevicesManagementApi.getDeviceReportsByDate(event.deviceId, 'switch_1', from.toString(), to.toString()); + await DevicesManagementApi.getDeviceReportsByDate( + event.deviceId, 'switch_1', from.toString(), to.toString()); emit(GarageDoorReportsState(deviceReport: records)); } catch (e) { emit(GarageDoorReportsFailedState(error: e.toString())); } } - void _backToGridView(BackToGarageDoorGridViewEvent event, Emitter emit) { + void _backToGridView( + BackToGarageDoorGridViewEvent event, Emitter emit) { emit(GarageDoorLoadedState(status: deviceStatus)); } - void _handleUpdate(GarageDoorUpdatedEvent event, Emitter emit) { + void _handleUpdate( + GarageDoorUpdatedEvent event, Emitter emit) { emit(GarageDoorLoadedState(status: deviceStatus)); } @@ -198,9 +244,11 @@ class GarageDoorBloc extends Bloc { late bool status; await Future.delayed(const Duration(milliseconds: 500)); if (isBatch) { - status = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); + status = await DevicesManagementApi() + .deviceBatchControl(deviceId, code, value); } else { - status = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); + status = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); } if (!status) { @@ -215,34 +263,47 @@ class GarageDoorBloc extends Bloc { } } - void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter emit) async { + void _increaseDelay( + IncreaseGarageDoorDelayEvent event, Emitter emit) async { // if (deviceStatus.countdown1 != 0) { try { - deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10)); + deviceStatus = deviceStatus.copyWith( + delay: deviceStatus.delay + Duration(minutes: 10)); emit(GarageDoorLoadedState(status: deviceStatus)); - add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1')); + add(GarageDoorControlEvent( + deviceId: event.deviceId, + value: deviceStatus.delay.inSeconds, + code: 'countdown_1')); } catch (e) { emit(GarageDoorErrorState(message: e.toString())); } // } } - void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter emit) async { + void _decreaseDelay( + DecreaseGarageDoorDelayEvent event, Emitter emit) async { // if (deviceStatus.countdown1 != 0) { try { if (deviceStatus.delay.inMinutes > 10) { - deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10)); + deviceStatus = deviceStatus.copyWith( + delay: deviceStatus.delay - Duration(minutes: 10)); } emit(GarageDoorLoadedState(status: deviceStatus)); - add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1')); + add(GarageDoorControlEvent( + deviceId: event.deviceId, + value: deviceStatus.delay.inSeconds, + code: 'countdown_1')); } catch (e) { emit(GarageDoorErrorState(message: e.toString())); } //} } - void _garageDoorControlEvent(GarageDoorControlEvent event, Emitter emit) async { - final oldValue = event.code == 'countdown_1' ? deviceStatus.countdown1 : deviceStatus.switch1; + void _garageDoorControlEvent( + GarageDoorControlEvent event, Emitter emit) async { + final oldValue = event.code == 'countdown_1' + ? deviceStatus.countdown1 + : deviceStatus.switch1; _updateLocalValue(event.code, event.value); emit(GarageDoorLoadedState(status: deviceStatus)); final success = await _runDeBouncer( @@ -258,7 +319,8 @@ class GarageDoorBloc extends Bloc { } } - void _revertValue(String code, dynamic oldValue, Emitter emit) { + void _revertValue( + String code, dynamic oldValue, Emitter emit) { switch (code) { case 'switch_1': if (oldValue is bool) { @@ -267,7 +329,8 @@ class GarageDoorBloc extends Bloc { break; case 'countdown_1': if (oldValue is int) { - deviceStatus = deviceStatus.copyWith(countdown1: oldValue, delay: Duration(seconds: oldValue)); + deviceStatus = deviceStatus.copyWith( + countdown1: oldValue, delay: Duration(seconds: oldValue)); } break; // Add other cases if needed @@ -289,10 +352,20 @@ class GarageDoorBloc extends Bloc { break; case 'countdown_1': if (value is int) { - deviceStatus = deviceStatus.copyWith(countdown1: value, delay: Duration(seconds: value)); + deviceStatus = deviceStatus.copyWith( + countdown1: value, delay: Duration(seconds: value)); + } + break; + case 'countdown_alarm': + if (value is int) { + deviceStatus = deviceStatus.copyWith(countdownAlarm: value); + } + break; + case 'tr_timecon': + if (value is int) { + deviceStatus = deviceStatus.copyWith(trTimeCon: value); } break; - // Add other cases if needed default: break; } diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart index 20758077..515b2a84 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart @@ -24,7 +24,8 @@ class GarageDoorControlEvent extends GarageDoorEvent { final dynamic value; final String code; - const GarageDoorControlEvent({required this.deviceId, required this.value, required this.code}); + const GarageDoorControlEvent( + {required this.deviceId, required this.value, required this.code}); @override List get props => [deviceId, value]; @@ -104,7 +105,8 @@ class FetchGarageDoorRecordsEvent extends GarageDoorEvent { final String deviceId; final String code; - const FetchGarageDoorRecordsEvent({required this.deviceId, required this.code}); + const FetchGarageDoorRecordsEvent( + {required this.deviceId, required this.code}); @override List get props => [deviceId, code]; @@ -166,3 +168,15 @@ class InitializeAddScheduleEvent extends GarageDoorEvent { index, ]; } + +class UpdateCountdownAlarmEvent extends GarageDoorEvent { + final int countdownAlarm; + + const UpdateCountdownAlarmEvent(this.countdownAlarm); +} + +class UpdateTrTimeConEvent extends GarageDoorEvent { + final int trTimeCon; + + const UpdateTrTimeConEvent(this.trTimeCon); +} diff --git a/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart b/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart index 55957b53..c30f391d 100644 --- a/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart +++ b/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart @@ -291,6 +291,9 @@ class GarageDoorDialogHelper { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const SizedBox(), + + /// The dialog is closed when the user taps on the close button or when the + /// [GarageDoorBloc] state changes. Text( 'Preferences', style: context.textTheme.titleLarge!.copyWith( @@ -311,13 +314,32 @@ class GarageDoorDialogHelper { child: GestureDetector( onTap: () { context.customAlertDialog( - alertBody: TimeOutAlarmDialogBody(), + alertBody: TimeOutAlarmDialogBody(bloc), title: 'Time Out Alarm', - onConfirm: () {}); + onConfirm: () { + final updatedState = + context.read().state; + if (updatedState + is GarageDoorLoadedState) { + context.read().add( + GarageDoorControlEvent( + deviceId: + updatedState.status.uuid, + code: 'countdown_alarm', + value: updatedState + .status.countdownAlarm, + ), + ); + Navigator.pop(context); + // context.read().add( + // GarageDoorInitialEvent( + // bloc.deviceId)); + } + }); }, child: ToggleWidget( icon: "-1", - value: bloc.deviceStatus.countdownAlarm > 0, + value: state.status.countdownAlarm > 0, code: 'countdown_alarm', deviceId: bloc.deviceId, label: 'Alarm when door is open', @@ -333,12 +355,38 @@ class GarageDoorDialogHelper { child: GestureDetector( onTap: () { context.customAlertDialog( - alertBody: OpeningClosingTimeDialogBody(), + alertBody: OpeningAndClosingTimeDialogBody( + bloc: bloc, + onDurationChanged: (newDuration) { + context.read().add( + UpdateTrTimeConEvent(newDuration), + ); + }, + ), title: 'Opening and Closing Time', - onConfirm: () {}); + onConfirm: () { + final updatedState = + context.read().state; + if (updatedState + is GarageDoorLoadedState) { + context.read().add( + GarageDoorControlEvent( + deviceId: + updatedState.status.uuid, + code: 'tr_timecon', + value: updatedState + .status.trTimeCon, + ), + ); + Navigator.pop(context); + // context.read().add( + // GarageDoorInitialEvent( + // bloc.deviceId)); + } + }); }, child: PresenceDisplayValue( - value: bloc.deviceStatus.trTimeCon.toString(), + value: state.status.trTimeCon.toString(), postfix: 'sec', description: 'Opening & Closing Time', ), diff --git a/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart b/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart index ea76d901..843bac9b 100644 --- a/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart +++ b/lib/pages/device_managment/garage_door/widgets/opening_clsoing_time_dialog_body.dart @@ -1,13 +1,53 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/widgets/seconds_picker.dart'; -class OpeningClosingTimeDialogBody extends StatelessWidget { - const OpeningClosingTimeDialogBody({super.key}); +class OpeningAndClosingTimeDialogBody extends StatefulWidget { + final ValueChanged onDurationChanged; + final GarageDoorBloc bloc; + + OpeningAndClosingTimeDialogBody({ + required this.onDurationChanged, + required this.bloc, + }); + + @override + _OpeningAndClosingTimeDialogBodyState createState() => + _OpeningAndClosingTimeDialogBodyState(); +} + +class _OpeningAndClosingTimeDialogBodyState + extends State { + late int durationInSeconds; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + final currentState = widget.bloc.state; + if (currentState is GarageDoorLoadedState) { + setState(() { + durationInSeconds = currentState.status.trTimeCon; + }); + } + } @override Widget build(BuildContext context) { return Container( - width: 350, - child: Text('asdasd'), + height: 120, + color: Colors.white, + child: SecondsPicker( + initialSeconds: durationInSeconds, + onSecondsChanged: (newSeconds) { + setState(() { + durationInSeconds = newSeconds; + }); + widget.onDurationChanged(newSeconds); + }, + ), ); } } diff --git a/lib/pages/device_managment/garage_door/widgets/seconds_picker.dart b/lib/pages/device_managment/garage_door/widgets/seconds_picker.dart new file mode 100644 index 00000000..491be37b --- /dev/null +++ b/lib/pages/device_managment/garage_door/widgets/seconds_picker.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; + +class SecondsPicker extends StatefulWidget { + final int initialSeconds; + final ValueChanged onSecondsChanged; + + SecondsPicker({ + required this.initialSeconds, + required this.onSecondsChanged, + }); + + @override + _SecondsPickerState createState() => _SecondsPickerState(); +} + +class _SecondsPickerState extends State { + late FixedExtentScrollController _scrollController; + + @override + void initState() { + super.initState(); + _scrollController = FixedExtentScrollController( + initialItem: widget.initialSeconds, + ); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 120, + color: Colors.white, + child: ListWheelScrollView.useDelegate( + controller: _scrollController, + itemExtent: 48, + onSelectedItemChanged: (index) { + widget.onSecondsChanged(index); + }, + physics: const FixedExtentScrollPhysics(), + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) { + return Center( + child: Text( + '$index sec', + style: const TextStyle(fontSize: 24), + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart b/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart index 948f8190..541ab9e4 100644 --- a/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart +++ b/lib/pages/device_managment/garage_door/widgets/time_out_alarm_dialog_body.dart @@ -1,13 +1,49 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; -class TimeOutAlarmDialogBody extends StatelessWidget { - const TimeOutAlarmDialogBody({super.key}); +class TimeOutAlarmDialogBody extends StatefulWidget { + TimeOutAlarmDialogBody(this.bloc); + final GarageDoorBloc bloc; + + @override + _TimeOutAlarmDialogBodyState createState() => _TimeOutAlarmDialogBodyState(); +} + +class _TimeOutAlarmDialogBodyState extends State { + int durationInSeconds = 0; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + final currentState = widget.bloc.state; + if (currentState is GarageDoorLoadedState) { + if (currentState.status.countdownAlarm != 0) { + setState(() { + durationInSeconds = currentState.status.countdownAlarm; + }); + } + } + } @override Widget build(BuildContext context) { return Container( - width: 350, - child: Text('asdasd'), + height: 120, + color: Colors.white, + child: CupertinoTimerPicker( + itemExtent: 120, + mode: CupertinoTimerPickerMode.hm, + initialTimerDuration: Duration(seconds: durationInSeconds), + onTimerDurationChanged: (newDuration) { + widget.bloc.add( + UpdateCountdownAlarmEvent(newDuration.inSeconds), + ); + }, + ), ); } } From e508849fa5114f894099abfae3eeae9864f05639 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 7 Oct 2024 09:52:16 +0300 Subject: [PATCH 57/65] push factory reset --- .../helper/route_controls_based_code.dart | 78 ++++++++++++--- .../garage_door/bloc/garage_door_bloc.dart | 54 +++++++++++ .../garage_door/bloc/garage_door_event.dart | 38 ++++++++ .../garage_door/bloc/garage_door_state.dart | 18 ++++ .../view/garage_door_batch_control_view.dart | 95 +++++++++++++++++++ .../bloc/one_gang_glass_switch_bloc.dart | 58 ++++++++--- .../bloc/one_gang_glass_switch_event.dart | 10 ++ .../one_gang_glass_batch_control_view.dart | 23 +++-- .../bloc/three_gang_glass_switch_event.dart | 3 +- .../bloc/two_gang_glass_switch_bloc.dart | 51 ++++++---- 10 files changed, 376 insertions(+), 52 deletions(-) diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index 3515ccf7..f9429bd6 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart'; @@ -110,56 +111,105 @@ mixin RouteControlsBasedCode { switch (devices.first.productType) { case '1G': return WallLightBatchControlView( - deviceIds: devices.where((e) => (e.productType == '1G')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == '1G')) + .map((e) => e.uuid!) + .toList(), ); case '2G': return TwoGangBatchControlView( - deviceIds: devices.where((e) => (e.productType == '2G')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == '2G')) + .map((e) => e.uuid!) + .toList(), ); case '3G': return LivingRoomBatchControlsView( - deviceIds: devices.where((e) => (e.productType == '3G')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == '3G')) + .map((e) => e.uuid!) + .toList(), ); case '1GT': return OneGangGlassSwitchBatchControlView( - deviceIds: devices.where((e) => (e.productType == '1GT')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == '1GT')) + .map((e) => e.uuid!) + .toList(), ); case '2GT': return TwoGangGlassSwitchBatchControlView( - deviceIds: devices.where((e) => (e.productType == '2GT')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == '2GT')) + .map((e) => e.uuid!) + .toList(), ); case '3GT': return ThreeGangGlassSwitchBatchControlView( - deviceIds: devices.where((e) => (e.productType == '3GT')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == '3GT')) + .map((e) => e.uuid!) + .toList(), ); case 'GW': return GatewayBatchControlView( - gatewayIds: devices.where((e) => (e.productType == 'GW')).map((e) => e.uuid!).toList(), + gatewayIds: devices + .where((e) => (e.productType == 'GW')) + .map((e) => e.uuid!) + .toList(), ); case 'DL': return DoorLockBatchControlView( - devicesIds: devices.where((e) => (e.productType == 'DL')).map((e) => e.uuid!).toList()); + devicesIds: devices + .where((e) => (e.productType == 'DL')) + .map((e) => e.uuid!) + .toList()); case 'WPS': return WallSensorBatchControlView( - devicesIds: devices.where((e) => (e.productType == 'WPS')).map((e) => e.uuid!).toList()); + devicesIds: devices + .where((e) => (e.productType == 'WPS')) + .map((e) => e.uuid!) + .toList()); case 'CPS': return CeilingSensorBatchControlView( - devicesIds: devices.where((e) => (e.productType == 'CPS')).map((e) => e.uuid!).toList(), + devicesIds: devices + .where((e) => (e.productType == 'CPS')) + .map((e) => e.uuid!) + .toList(), ); case 'CUR': return CurtainBatchStatusView( - devicesIds: devices.where((e) => (e.productType == 'CUR')).map((e) => e.uuid!).toList(), + devicesIds: devices + .where((e) => (e.productType == 'CUR')) + .map((e) => e.uuid!) + .toList(), ); case 'AC': return AcDeviceBatchControlView( - devicesIds: devices.where((e) => (e.productType == 'AC')).map((e) => e.uuid!).toList()); + devicesIds: devices + .where((e) => (e.productType == 'AC')) + .map((e) => e.uuid!) + .toList()); case 'WH': return WaterHEaterBatchControlView( - deviceIds: devices.where((e) => (e.productType == 'WH')).map((e) => e.uuid!).toList(), + deviceIds: devices + .where((e) => (e.productType == 'WH')) + .map((e) => e.uuid!) + .toList(), ); case 'DS': return MainDoorSensorBatchView( - devicesIds: devices.where((e) => (e.productType == 'DS')).map((e) => e.uuid!).toList(), + devicesIds: devices + .where((e) => (e.productType == 'DS')) + .map((e) => e.uuid!) + .toList(), + ); + case 'GD': + return GarageDoorBatchControlView( + deviceIds: devices + .where((e) => (e.productType == 'GD')) + .map((e) => e.uuid!) + .toList(), ); default: return const SizedBox(); diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index 04af1e39..69e58c65 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -35,6 +35,9 @@ class GarageDoorBloc extends Bloc { on(_backToGridView); on(_onUpdateCountdownAlarm); on(_onUpdateTrTimeCon); + on(_onBatchControl); + on(_onFetchBatchStatus); + on(_onFactoryReset); } void _fetchGarageDoorStatus( @@ -50,6 +53,20 @@ class GarageDoorBloc extends Bloc { } } + Future _onFetchBatchStatus(GarageDoorFetchBatchStatusEvent event, + Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + final status = + await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = + GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status); + emit(GarageDoorBatchStatusLoaded(deviceStatus)); + } catch (e) { + emit(GarageDoorBatchControlError(e.toString())); + } + } + Future _addSchedule( AddGarageDoorScheduleEvent event, Emitter emit) async { try { @@ -222,6 +239,27 @@ class GarageDoorBloc extends Bloc { } } + Future _onBatchControl( + GarageDoorBatchControlEvent event, Emitter emit) async { + final oldValue = event.code == 'switch_1' ? deviceStatus.switch1 : false; + + _updateLocalValue(event.code, event.value); + emit(GarageDoorBatchStatusLoaded(deviceStatus)); + + final success = await _runDeBouncer( + deviceId: event.deviceIds, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: true, + ); + + if (!success) { + _revertValue(event.code, oldValue, emit); + } + } + void _backToGridView( BackToGarageDoorGridViewEvent event, Emitter emit) { emit(GarageDoorLoadedState(status: deviceStatus)); @@ -263,6 +301,22 @@ class GarageDoorBloc extends Bloc { } } + Future _onFactoryReset( + GarageDoorFactoryResetEvent event, Emitter emit) async { + emit(GarageDoorLoadingState()); + try { + final response = await DevicesManagementApi() + .factoryReset(event.factoryReset, event.deviceId); + if (!response) { + emit(const GarageDoorErrorState(message: 'Failed to reset device')); + } else { + emit(GarageDoorLoadedState(status: deviceStatus)); + } + } catch (e) { + emit(GarageDoorErrorState(message: e.toString())); + } + } + void _increaseDelay( IncreaseGarageDoorDelayEvent event, Emitter emit) async { // if (deviceStatus.countdown1 != 0) { diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart index 515b2a84..24eb921e 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart @@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; abstract class GarageDoorEvent extends Equatable { const GarageDoorEvent(); @@ -180,3 +181,40 @@ class UpdateTrTimeConEvent extends GarageDoorEvent { const UpdateTrTimeConEvent(this.trTimeCon); } + +class GarageDoorBatchControlEvent extends GarageDoorEvent { + final List deviceIds; + final String code; + final bool value; + + const GarageDoorBatchControlEvent({ + required this.deviceIds, + required this.code, + required this.value, + }); + + @override + List get props => [deviceIds, code, value]; +} + +class GarageDoorFetchBatchStatusEvent extends GarageDoorEvent { + final List deviceIds; + + const GarageDoorFetchBatchStatusEvent(this.deviceIds); + + @override + List get props => [deviceIds]; +} + +class GarageDoorFactoryResetEvent extends GarageDoorEvent { + final FactoryResetModel factoryReset; + final String deviceId; + + const GarageDoorFactoryResetEvent({ + required this.factoryReset, + required this.deviceId, + }); + + @override + List get props => [factoryReset, deviceId]; +} diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart index 6f3a2320..2b63a3f8 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_state.dart @@ -121,3 +121,21 @@ class ShowGarageDoorDescriptionState extends GarageDoorState { } class ScheduleGarageLoadingState extends GarageDoorState {} + +class GarageDoorBatchStatusLoaded extends GarageDoorState { + final GarageDoorStatusModel status; + + const GarageDoorBatchStatusLoaded(this.status); + + @override + List get props => [status]; +} + +class GarageDoorBatchControlError extends GarageDoorState { + final String message; + + const GarageDoorBatchControlError(this.message); + + @override + List get props => [message]; +} diff --git a/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart b/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart index e69de29b..8c8b60cf 100644 --- a/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart +++ b/lib/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart'; +import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class GarageDoorBatchControlView extends StatelessWidget + with HelperResponsiveLayout { + final List deviceIds; + + const GarageDoorBatchControlView({Key? key, required this.deviceIds}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => GarageDoorBloc(deviceId: deviceIds.first) + ..add(GarageDoorFetchBatchStatusEvent(deviceIds)), + child: BlocBuilder( + builder: (context, state) { + if (state is GarageDoorLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is GarageDoorBatchStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is GarageDoorBatchControlError) { + return Center(child: Text('Error: ${state.message}')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls( + BuildContext context, GarageDoorStatusModel 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, + ), + children: [ + ToggleWidget( + value: status.switch1, + code: 'switch_1', + deviceId: deviceIds.first, + label: 'Garage Door', + icon: status.switch1 ? Assets.openedDoor : Assets.closedDoor, + onChange: (value) { + context.read().add( + GarageDoorBatchControlEvent( + deviceIds: deviceIds, + code: 'switch_1', + value: value, + ), + ); + }, + ), + FirmwareUpdateWidget( + deviceId: deviceIds.first, + version: 12, + ), + FactoryResetWidget( + callFactoryReset: () { + context.read().add( + GarageDoorFactoryResetEvent( + deviceId: deviceIds.first, + factoryReset: FactoryResetModel(devicesUuid: deviceIds), + ), + ); + }, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart index 111d5014..b7760333 100644 --- a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart +++ b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart @@ -3,38 +3,45 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:meta/meta.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; part 'one_gang_glass_switch_event.dart'; part 'one_gang_glass_switch_state.dart'; -class OneGangGlassSwitchBloc extends Bloc { +class OneGangGlassSwitchBloc + extends Bloc { OneGangGlassStatusModel deviceStatus; Timer? _timer; OneGangGlassSwitchBloc({required String deviceId}) - : deviceStatus = OneGangGlassStatusModel(uuid: deviceId, switch1: false, countDown: 0), + : deviceStatus = OneGangGlassStatusModel( + uuid: deviceId, switch1: false, countDown: 0), super(OneGangGlassSwitchInitial()) { on(_onFetchDeviceStatus); on(_onControl); on(_onBatchControl); on(_onFetchBatchStatus); + on(_onFactoryReset); } - Future _onFetchDeviceStatus( - OneGangGlassSwitchFetchDeviceEvent event, Emitter emit) async { + Future _onFetchDeviceStatus(OneGangGlassSwitchFetchDeviceEvent event, + Emitter emit) async { emit(OneGangGlassSwitchLoading()); try { - final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status); + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + OneGangGlassStatusModel.fromJson(event.deviceId, status.status); emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(OneGangGlassSwitchError(e.toString())); } } - Future _onControl(OneGangGlassSwitchControl event, Emitter emit) async { + Future _onControl(OneGangGlassSwitchControl event, + Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -50,7 +57,24 @@ class OneGangGlassSwitchBloc extends Bloc _onBatchControl(OneGangGlassSwitchBatchControl event, Emitter emit) async { + Future _onFactoryReset(OneGangGlassFactoryResetEvent event, + Emitter emit) async { + emit(OneGangGlassSwitchLoading()); + try { + final response = await DevicesManagementApi() + .factoryReset(event.factoryReset, event.deviceId); + if (!response) { + emit(OneGangGlassSwitchError('Failed to reset device')); + } else { + emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); + } + } catch (e) { + emit(OneGangGlassSwitchError(e.toString())); + } + } + + Future _onBatchControl(OneGangGlassSwitchBatchControl event, + Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -67,11 +91,14 @@ class OneGangGlassSwitchBloc extends Bloc _onFetchBatchStatus( - OneGangGlassSwitchFetchBatchStatusEvent event, Emitter emit) async { + OneGangGlassSwitchFetchBatchStatusEvent event, + Emitter emit) async { emit(OneGangGlassSwitchLoading()); try { - final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); - deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceIds.first, status.status); + final status = + await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = OneGangGlassStatusModel.fromJson( + event.deviceIds.first, status.status); emit(OneGangGlassSwitchBatchStatusLoaded(deviceStatus)); } catch (e) { emit(OneGangGlassSwitchError(e.toString())); @@ -101,9 +128,11 @@ class OneGangGlassSwitchBloc extends Bloc emit) { + void _revertValueAndEmit(String deviceId, String code, bool oldValue, + Emitter emit) { _updateLocalValue(code, oldValue); emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); } diff --git a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart index 993e5a8c..83d9b7b9 100644 --- a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart +++ b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_event.dart @@ -38,3 +38,13 @@ class OneGangGlassSwitchFetchBatchStatusEvent extends OneGangGlassSwitchEvent { OneGangGlassSwitchFetchBatchStatusEvent(this.deviceIds); } + +class OneGangGlassFactoryResetEvent extends OneGangGlassSwitchEvent { + final FactoryResetModel factoryReset; + final String deviceId; + + OneGangGlassFactoryResetEvent({ + required this.factoryReset, + required this.deviceId, + }); +} diff --git a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart index e81f448c..7c0064c3 100644 --- a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart +++ b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; @@ -7,16 +8,18 @@ import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResponsiveLayout { +class OneGangGlassSwitchBatchControlView extends StatelessWidget + with HelperResponsiveLayout { final List deviceIds; - const OneGangGlassSwitchBatchControlView({required this.deviceIds, super.key}); + const OneGangGlassSwitchBatchControlView( + {required this.deviceIds, super.key}); @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => - OneGangGlassSwitchBloc(deviceId: deviceIds.first)..add(OneGangGlassSwitchFetchBatchStatusEvent(deviceIds)), + create: (context) => OneGangGlassSwitchBloc(deviceId: deviceIds.first) + ..add(OneGangGlassSwitchFetchBatchStatusEvent(deviceIds)), child: BlocBuilder( builder: (context, state) { if (state is OneGangGlassSwitchLoading) { @@ -33,7 +36,8 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp ); } - Widget _buildStatusControls(BuildContext context, OneGangGlassStatusModel status) { + Widget _buildStatusControls( + BuildContext context, OneGangGlassStatusModel status) { final isExtraLarge = isExtraLargeScreenSize(context); final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); @@ -72,7 +76,14 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp version: 12, // adjust the version according to your requirement ), FactoryResetWidget( - callFactoryReset: () {}, + callFactoryReset: () { + context.read().add( + OneGangGlassFactoryResetEvent( + factoryReset: FactoryResetModel(devicesUuid: deviceIds), + deviceId: deviceIds.first, + ), + ); + }, ), ], ); diff --git a/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart index a40888e7..558b9824 100644 --- a/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart +++ b/lib/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_event.dart @@ -33,7 +33,8 @@ class ThreeGangGlassSwitchBatchControl extends ThreeGangGlassSwitchEvent { }); } -class ThreeGangGlassSwitchFetchBatchStatusEvent extends ThreeGangGlassSwitchEvent { +class ThreeGangGlassSwitchFetchBatchStatusEvent + extends ThreeGangGlassSwitchEvent { final List deviceIds; ThreeGangGlassSwitchFetchBatchStatusEvent(this.deviceIds); diff --git a/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart index be2ab687..5169b0e4 100644 --- a/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart +++ b/lib/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart @@ -10,13 +10,18 @@ import 'package:syncrow_web/services/devices_mang_api.dart'; part 'two_gang_glass_switch_event.dart'; part 'two_gang_glass_switch_state.dart'; -class TwoGangGlassSwitchBloc extends Bloc { +class TwoGangGlassSwitchBloc + extends Bloc { TwoGangGlassStatusModel deviceStatus; Timer? _timer; TwoGangGlassSwitchBloc({required String deviceId}) - : deviceStatus = - TwoGangGlassStatusModel(uuid: deviceId, switch1: false, countDown1: 0, switch2: false, countDown2: 0), + : deviceStatus = TwoGangGlassStatusModel( + uuid: deviceId, + switch1: false, + countDown1: 0, + switch2: false, + countDown2: 0), super(TwoGangGlassSwitchInitial()) { on(_onFetchDeviceStatus); on(_onControl); @@ -25,19 +30,22 @@ class TwoGangGlassSwitchBloc extends Bloc(_onFactoryReset); } - Future _onFetchDeviceStatus( - TwoGangGlassSwitchFetchDeviceEvent event, Emitter emit) async { + Future _onFetchDeviceStatus(TwoGangGlassSwitchFetchDeviceEvent event, + Emitter emit) async { emit(TwoGangGlassSwitchLoading()); try { - final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceId, status.status); + final status = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = + TwoGangGlassStatusModel.fromJson(event.deviceId, status.status); emit(TwoGangGlassSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(TwoGangGlassSwitchError(e.toString())); } } - Future _onControl(TwoGangGlassSwitchControl event, Emitter emit) async { + Future _onControl(TwoGangGlassSwitchControl event, + Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -53,7 +61,8 @@ class TwoGangGlassSwitchBloc extends Bloc _onBatchControl(TwoGangGlassSwitchBatchControl event, Emitter emit) async { + Future _onBatchControl(TwoGangGlassSwitchBatchControl event, + Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -70,21 +79,26 @@ class TwoGangGlassSwitchBloc extends Bloc _onFetchBatchStatus( - TwoGangGlassSwitchFetchBatchStatusEvent event, Emitter emit) async { + TwoGangGlassSwitchFetchBatchStatusEvent event, + Emitter emit) async { emit(TwoGangGlassSwitchLoading()); try { - final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); - deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceIds.first, status.status); + final status = + await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = TwoGangGlassStatusModel.fromJson( + event.deviceIds.first, status.status); emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus)); } catch (e) { emit(TwoGangGlassSwitchError(e.toString())); } } - Future _onFactoryReset(TwoGangGlassFactoryReset event, Emitter emit) async { + Future _onFactoryReset(TwoGangGlassFactoryReset event, + Emitter emit) async { emit(TwoGangGlassSwitchLoading()); try { - final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId); + final response = await DevicesManagementApi() + .factoryReset(event.factoryReset, event.deviceId); if (!response) { emit(TwoGangGlassSwitchError('Failed')); } else { @@ -118,9 +132,11 @@ class TwoGangGlassSwitchBloc extends Bloc emit) { + void _revertValueAndEmit(String deviceId, String code, bool oldValue, + Emitter emit) { _updateLocalValue(code, oldValue); emit(TwoGangGlassSwitchStatusLoaded(deviceStatus)); } From 2b5249e985b8944be6f63029ba28f58c6be521d1 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 7 Oct 2024 10:06:28 +0300 Subject: [PATCH 58/65] push firmware update --- .../one_gang_glass_batch_control_view.dart | 2 +- .../shared/batch_control/firmware_update.dart | 135 ++++++++++++++---- 2 files changed, 110 insertions(+), 27 deletions(-) diff --git a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart index 7c0064c3..4239b08e 100644 --- a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart +++ b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart @@ -73,7 +73,7 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget ), FirmwareUpdateWidget( deviceId: deviceIds.first, - version: 12, // adjust the version according to your requirement + version: 12, ), FactoryResetWidget( callFactoryReset: () { diff --git a/lib/pages/device_managment/shared/batch_control/firmware_update.dart b/lib/pages/device_managment/shared/batch_control/firmware_update.dart index bb7c7516..095d3efc 100644 --- a/lib/pages/device_managment/shared/batch_control/firmware_update.dart +++ b/lib/pages/device_managment/shared/batch_control/firmware_update.dart @@ -1,43 +1,126 @@ import 'package:flutter/material.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/shared/device_controls_container.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 FirmwareUpdateWidget extends StatelessWidget { +class FirmwareUpdateWidget extends StatefulWidget { const FirmwareUpdateWidget( - {super.key, required String deviceId, required int version}); + {super.key, required this.deviceId, required this.version}); + + final String deviceId; + final int version; + + @override + State createState() => _FirmwareUpdateWidgetState(); +} + +class _FirmwareUpdateWidgetState extends State { + bool _showConfirmation = false; + + void _toggleConfirmation() { + setState(() { + _showConfirmation = !_showConfirmation; + }); + } @override Widget build(BuildContext context) { return DeviceControlsContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ClipOval( - child: Container( - color: ColorsManager.whiteColors, - height: 60, - width: 60, - child: Padding( - padding: const EdgeInsets.all(12.0), - child: SvgPicture.asset( - Assets.firmware, - fit: BoxFit.cover, + child: _showConfirmation + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Firmware Update', + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.blackColor, + ), + ), + Text( + 'Are you sure?', + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor, + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Flexible( + child: DefaultButton( + height: 20, + elevation: 0, + padding: 0, + onPressed: _toggleConfirmation, + backgroundColor: ColorsManager.greyColor, + child: Text( + 'Cancel', + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + ), + ), + const SizedBox(width: 8), + Flexible( + child: DefaultButton( + height: 20, + elevation: 0, + padding: 0, + onPressed: () { + _toggleConfirmation(); + }, + backgroundColor: ColorsManager.primaryColor, + child: Text( + 'Update', + style: context.textTheme.bodyMedium!.copyWith( + color: ColorsManager.whiteColors, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + ), + ), + ], + ), + ], + ) + : GestureDetector( + onTap: _toggleConfirmation, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ClipOval( + child: Container( + color: ColorsManager.whiteColors, + height: 60, + width: 60, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: SvgPicture.asset( + Assets.firmware, + fit: BoxFit.cover, + ), + ), + ), + ), + Text( + 'Firmware Update', + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: ColorsManager.blackColor, + ), + ), + ], ), ), - )), - Text( - 'Firmware Update', - style: context.textTheme.titleMedium!.copyWith( - fontWeight: FontWeight.w400, - color: ColorsManager.blackColor, - ), - ), - ], - ), ); } } From 9ea8e3be2b6462e69b15e8f8336667ea48b4df24 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Mon, 7 Oct 2024 11:26:30 +0300 Subject: [PATCH 59/65] push water leak device --- assets/icons/automation_records.svg | 17 ++ assets/icons/water_leak_detected.svg | 11 ++ assets/icons/water_leak_normal.svg | 11 ++ .../helper/route_controls_based_code.dart | 13 ++ .../shared/table/report_table.dart | 11 +- .../water_leak/bloc/water_leak_bloc.dart | 173 ++++++++++++++++++ .../water_leak/bloc/water_leak_event.dart | 87 +++++++++ .../water_leak/bloc/water_leak_state.dart | 61 ++++++ .../model/water_leak_status_model.dart | 48 +++++ .../view/water_leak_batch_control_view.dart | 67 +++++++ .../view/water_leak_control_view.dart | 128 +++++++++++++ .../widgets/water_leak_notifi_dialog.dart | 89 +++++++++ lib/utils/constants/assets.dart | 83 ++++++--- 13 files changed, 772 insertions(+), 27 deletions(-) create mode 100644 assets/icons/automation_records.svg create mode 100644 assets/icons/water_leak_detected.svg create mode 100644 assets/icons/water_leak_normal.svg create mode 100644 lib/pages/device_managment/water_leak/bloc/water_leak_bloc.dart create mode 100644 lib/pages/device_managment/water_leak/bloc/water_leak_event.dart create mode 100644 lib/pages/device_managment/water_leak/bloc/water_leak_state.dart create mode 100644 lib/pages/device_managment/water_leak/model/water_leak_status_model.dart create mode 100644 lib/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart create mode 100644 lib/pages/device_managment/water_leak/view/water_leak_control_view.dart create mode 100644 lib/pages/device_managment/water_leak/widgets/water_leak_notifi_dialog.dart diff --git a/assets/icons/automation_records.svg b/assets/icons/automation_records.svg new file mode 100644 index 00000000..2f5a1038 --- /dev/null +++ b/assets/icons/automation_records.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/icons/water_leak_detected.svg b/assets/icons/water_leak_detected.svg new file mode 100644 index 00000000..3d6f13b0 --- /dev/null +++ b/assets/icons/water_leak_detected.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/water_leak_normal.svg b/assets/icons/water_leak_normal.svg new file mode 100644 index 00000000..f1165c07 --- /dev/null +++ b/assets/icons/water_leak_normal.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart index f9429bd6..d4b5b21a 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -30,6 +30,8 @@ import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_ import 'package:syncrow_web/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_batch_control.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heater_device_control.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_control_view.dart'; import '../../one_g_glass_switch/view/one_gang_glass_switch_control_view.dart'; @@ -88,6 +90,10 @@ mixin RouteControlsBasedCode { return GarageDoorControlView( deviceId: device.uuid!, ); + case 'WL': + return WaterLeakView( + deviceId: device.uuid!, + ); default: return const SizedBox(); } @@ -211,6 +217,13 @@ mixin RouteControlsBasedCode { .map((e) => e.uuid!) .toList(), ); + case 'WL': + return WaterLeakBatchControlView( + deviceIds: devices + .where((e) => (e.productType == 'WL')) + .map((e) => e.uuid!) + .toList(), + ); default: return const SizedBox(); } diff --git a/lib/pages/device_managment/shared/table/report_table.dart b/lib/pages/device_managment/shared/table/report_table.dart index 527ae783..892bdd1a 100644 --- a/lib/pages/device_managment/shared/table/report_table.dart +++ b/lib/pages/device_managment/shared/table/report_table.dart @@ -14,6 +14,7 @@ class ReportsTable extends StatelessWidget { bool? hideValueShowDescription; bool? mainDoorSensor; bool? garageDoorSensor; + bool? waterLeak; ReportsTable({ super.key, @@ -25,6 +26,7 @@ class ReportsTable extends StatelessWidget { this.hideValueShowDescription, this.mainDoorSensor, this.garageDoorSensor, + this.waterLeak, }); @override @@ -55,7 +57,8 @@ class ReportsTable extends StatelessWidget { DeviceEvent data = entry.value; // Parse eventTime into Date and Time - DateTime eventDateTime = DateTime.fromMillisecondsSinceEpoch(data.eventTime!); + DateTime eventDateTime = + DateTime.fromMillisecondsSinceEpoch(data.eventTime!); String date = DateFormat('dd/MM/yyyy').format(eventDateTime); String time = DateFormat('HH:mm').format(eventDateTime); @@ -63,8 +66,12 @@ class ReportsTable extends StatelessWidget { if (hideValueShowDescription == true) { if (mainDoorSensor != null && mainDoorSensor == true) { value = data.value == 'true' ? 'Open' : 'Close'; - } else if (garageDoorSensor != null && garageDoorSensor == true) { + } else if (garageDoorSensor != null && + garageDoorSensor == true) { value = data.value == 'true' ? 'Opened' : 'Closed'; + } else if (waterLeak != null && waterLeak == true) { + value = + data.value == 'normal' ? 'Normal' : 'Leak Detected'; } else { value = '${data.value!} ${thirdColumnDescription ?? ''}'; } diff --git a/lib/pages/device_managment/water_leak/bloc/water_leak_bloc.dart b/lib/pages/device_managment/water_leak/bloc/water_leak_bloc.dart new file mode 100644 index 00000000..f1063dc9 --- /dev/null +++ b/lib/pages/device_managment/water_leak/bloc/water_leak_bloc.dart @@ -0,0 +1,173 @@ +import 'package:bloc/bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_event.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_state.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/model/water_leak_status_model.dart'; + +import 'dart:async'; + +import 'package:syncrow_web/services/devices_mang_api.dart'; + +class WaterLeakBloc extends Bloc { + WaterLeakStatusModel? deviceStatus; + Timer? _timer; + final String deviceId; + + WaterLeakBloc(this.deviceId) : super(WaterLeakInitialState()) { + on(_onFetchWaterLeakStatus); + on(_onControl); + on(_onBatchControl); + on(_onFetchBatchStatus); + on(_onFetchWaterLeakReports); + on(_onFactoryReset); + } + + Future _onFetchWaterLeakStatus( + FetchWaterLeakStatusEvent event, Emitter emit) async { + emit(WaterLeakLoadingState()); + try { + final response = + await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = WaterLeakStatusModel.fromJson(deviceId, response.status); + emit(WaterLeakLoadedState(deviceStatus!)); + } catch (e) { + emit(WaterLeakErrorState(e.toString())); + } + } + + Future _onControl( + WaterLeakControlEvent event, Emitter emit) async { + final oldValue = deviceStatus!.watersensorState; + + _updateLocalValue(event.code, event.value); + emit(WaterLeakLoadedState(deviceStatus!)); + + await _runDebounce( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: false, + ); + } + + Future _onFactoryReset( + WaterLeakFactoryResetEvent event, Emitter emit) async { + emit(WaterLeakLoadingState()); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryReset, + event.deviceId, + ); + if (response) { + emit(WaterLeakInitialState()); + } else { + emit(const WaterLeakErrorState('Factory reset failed')); + } + } catch (e) { + emit(WaterLeakErrorState(e.toString())); + } + } + + Future _onBatchControl( + WaterLeakBatchControlEvent event, Emitter emit) async { + final oldValue = deviceStatus!.watersensorState; + + _updateLocalValue(event.code, event.value); + emit(WaterLeakBatchStatusLoadedState(deviceStatus!)); + + await _runDebounce( + deviceId: event.deviceIds, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + isBatch: true, + ); + } + + Future _onFetchBatchStatus(FetchWaterLeakBatchStatusEvent event, + Emitter emit) async { + emit(WaterLeakLoadingState()); + try { + final response = + await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = WaterLeakStatusModel.fromJson(deviceId, response.status); + emit(WaterLeakBatchStatusLoadedState(deviceStatus!)); + } catch (e) { + emit(WaterLeakErrorState(e.toString())); + } + } + + Future _runDebounce({ + required dynamic deviceId, + required String code, + required dynamic value, + required dynamic oldValue, + required Emitter emit, + required bool isBatch, + }) async { + late String id; + if (deviceId is List) { + id = deviceId.first; + } else { + id = deviceId; + } + + if (_timer != null) { + _timer!.cancel(); + } + + _timer = Timer(const Duration(milliseconds: 500), () async { + try { + late bool response; + if (isBatch) { + response = await DevicesManagementApi() + .deviceBatchControl(deviceId, code, value); + } else { + response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + } + + if (!response) { + _revertValueAndEmit(id, code, oldValue, emit); + } + } catch (e) { + _revertValueAndEmit(id, code, oldValue, emit); + } + }); + } + + void _updateLocalValue(String code, dynamic value) { + if (code == 'watersensor_state') { + deviceStatus = deviceStatus!.copyWith(watersensorState: value); + } else if (code == 'battery_percentage') { + deviceStatus = deviceStatus!.copyWith(batteryPercentage: value); + } + } + + void _revertValueAndEmit(String deviceId, String code, dynamic oldValue, + Emitter emit) { + _updateLocalValue(code, oldValue); + emit(WaterLeakLoadedState(deviceStatus!)); + } + + Future _onFetchWaterLeakReports( + FetchWaterLeakReportsEvent event, Emitter emit) async { + emit(WaterLeakReportsLoadingState()); + try { + final from = DateTime.now() + .subtract(const Duration(days: 30)) + .millisecondsSinceEpoch; + final to = DateTime.now().millisecondsSinceEpoch; + final DeviceReport records = + await DevicesManagementApi.getDeviceReportsByDate( + event.deviceId, event.code, from.toString(), to.toString()); + emit(WaterLeakReportsLoadedState(records)); + } catch (e) { + emit(WaterLeakReportsFailedState(e.toString())); + } + } +} diff --git a/lib/pages/device_managment/water_leak/bloc/water_leak_event.dart b/lib/pages/device_managment/water_leak/bloc/water_leak_event.dart new file mode 100644 index 00000000..9c048280 --- /dev/null +++ b/lib/pages/device_managment/water_leak/bloc/water_leak_event.dart @@ -0,0 +1,87 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; + +abstract class WaterLeakEvent extends Equatable { + const WaterLeakEvent(); + + @override + List get props => []; +} + +class FetchWaterLeakStatusEvent extends WaterLeakEvent { + final String deviceId; + + const FetchWaterLeakStatusEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class WaterLeakControlEvent extends WaterLeakEvent { + final String deviceId; + final String code; + final dynamic value; + + const WaterLeakControlEvent({ + required this.deviceId, + required this.code, + required this.value, + }); + + @override + List get props => [deviceId, code, value]; +} + +class WaterLeakBatchControlEvent extends WaterLeakEvent { + final List deviceIds; + final String code; + final dynamic value; + + const WaterLeakBatchControlEvent({ + required this.deviceIds, + required this.code, + required this.value, + }); + + @override + List get props => [deviceIds, code, value]; +} + +class FetchWaterLeakBatchStatusEvent extends WaterLeakEvent { + final List deviceIds; + + const FetchWaterLeakBatchStatusEvent(this.deviceIds); + + @override + List get props => [deviceIds]; +} + +class FetchWaterLeakReportsEvent extends WaterLeakEvent { + final String deviceId; + final String code; + final int from; + final int to; + + const FetchWaterLeakReportsEvent({ + required this.deviceId, + required this.code, + required this.from, + required this.to, + }); + + @override + List get props => [deviceId, code, from, to]; +} + +class WaterLeakFactoryResetEvent extends WaterLeakEvent { + final String deviceId; + final FactoryResetModel factoryReset; + + const WaterLeakFactoryResetEvent({ + required this.deviceId, + required this.factoryReset, + }); + + @override + List get props => [deviceId]; +} diff --git a/lib/pages/device_managment/water_leak/bloc/water_leak_state.dart b/lib/pages/device_managment/water_leak/bloc/water_leak_state.dart new file mode 100644 index 00000000..e1dc0de0 --- /dev/null +++ b/lib/pages/device_managment/water_leak/bloc/water_leak_state.dart @@ -0,0 +1,61 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/model/water_leak_status_model.dart'; + +abstract class WaterLeakState extends Equatable { + const WaterLeakState(); + + @override + List get props => []; +} + +class WaterLeakInitialState extends WaterLeakState {} + +class WaterLeakLoadingState extends WaterLeakState {} + +class WaterLeakLoadedState extends WaterLeakState { + final WaterLeakStatusModel status; + + const WaterLeakLoadedState(this.status); + + @override + List get props => [status]; +} + +class WaterLeakBatchStatusLoadedState extends WaterLeakState { + final WaterLeakStatusModel status; + + const WaterLeakBatchStatusLoadedState(this.status); + + @override + List get props => [status]; +} + +class WaterLeakErrorState extends WaterLeakState { + final String message; + + const WaterLeakErrorState(this.message); + + @override + List get props => [message]; +} + +class WaterLeakReportsLoadingState extends WaterLeakState {} + +class WaterLeakReportsLoadedState extends WaterLeakState { + final DeviceReport deviceReport; + + const WaterLeakReportsLoadedState(this.deviceReport); + + @override + List get props => [deviceReport]; +} + +class WaterLeakReportsFailedState extends WaterLeakState { + final String error; + + const WaterLeakReportsFailedState(this.error); + + @override + List get props => [error]; +} diff --git a/lib/pages/device_managment/water_leak/model/water_leak_status_model.dart b/lib/pages/device_managment/water_leak/model/water_leak_status_model.dart new file mode 100644 index 00000000..d496c969 --- /dev/null +++ b/lib/pages/device_managment/water_leak/model/water_leak_status_model.dart @@ -0,0 +1,48 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class WaterLeakStatusModel { + final String productUuid; + final String productType; + final String watersensorState; + final int batteryPercentage; + + WaterLeakStatusModel({ + required this.productUuid, + required this.productType, + required this.watersensorState, + required this.batteryPercentage, + }); + factory WaterLeakStatusModel.fromJson(String id, List jsonList) { + late String watersensorState; + late int batteryPercentage; + + for (var i = 0; i < jsonList.length; i++) { + if (jsonList[i].code == 'watersensor_state') { + watersensorState = jsonList[i].value; + } else if (jsonList[i].code == 'battery_percentage') { + batteryPercentage = jsonList[i].value; + } + } + + return WaterLeakStatusModel( + productUuid: id, + productType: 'WL', + watersensorState: watersensorState, + batteryPercentage: batteryPercentage, + ); + } + + WaterLeakStatusModel copyWith({ + String? productUuid, + String? productType, + String? watersensorState, + int? batteryPercentage, + }) { + return WaterLeakStatusModel( + productUuid: productUuid ?? this.productUuid, + productType: productType ?? this.productType, + watersensorState: watersensorState ?? this.watersensorState, + batteryPercentage: batteryPercentage ?? this.batteryPercentage, + ); + } +} diff --git a/lib/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart b/lib/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart new file mode 100644 index 00000000..9d2c030f --- /dev/null +++ b/lib/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; + +import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_event.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_state.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/model/water_leak_status_model.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class WaterLeakBatchControlView extends StatelessWidget + with HelperResponsiveLayout { + final List deviceIds; + + const WaterLeakBatchControlView({Key? key, required this.deviceIds}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => WaterLeakBloc(deviceIds.first) + ..add(FetchWaterLeakBatchStatusEvent(deviceIds)), + child: BlocBuilder( + builder: (context, state) { + if (state is WaterLeakLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is WaterLeakBatchStatusLoadedState) { + return _buildStatusControls(context, state.status); + } else if (state is WaterLeakErrorState) { + return Center(child: Text('Error: ${state.message}')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls( + BuildContext context, WaterLeakStatusModel status) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 170, + height: 140, + child: FirmwareUpdateWidget(deviceId: deviceIds.first, version: 2)), + const SizedBox( + width: 12, + ), + SizedBox( + width: 170, + height: 140, + child: FactoryResetWidget( + callFactoryReset: () { + context.read().add(WaterLeakFactoryResetEvent( + deviceId: deviceIds.first, + factoryReset: FactoryResetModel(devicesUuid: deviceIds))); + }, + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/water_leak/view/water_leak_control_view.dart b/lib/pages/device_managment/water_leak/view/water_leak_control_view.dart new file mode 100644 index 00000000..58421422 --- /dev/null +++ b/lib/pages/device_managment/water_leak/view/water_leak_control_view.dart @@ -0,0 +1,128 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_event.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/bloc/water_leak_state.dart'; +import 'package:syncrow_web/pages/device_managment/water_leak/widgets/water_leak_notifi_dialog.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class WaterLeakView extends StatelessWidget with HelperResponsiveLayout { + final String deviceId; + + const WaterLeakView({Key? key, required this.deviceId}) : super(key: key); + + @override + Widget build(BuildContext context) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return BlocProvider( + create: (context) => + WaterLeakBloc(deviceId)..add(FetchWaterLeakStatusEvent(deviceId)), + child: BlocBuilder( + builder: (context, state) { + if (state is WaterLeakLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is WaterLeakLoadedState) { + return GridView( + shrinkWrap: true, + padding: const EdgeInsets.symmetric(horizontal: 50), + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isExtraLarge || isLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + IconNameStatusContainer( + isFullIcon: false, + name: state.status.watersensorState == 'normal' + ? 'Normal' + : 'Leak Detection', + icon: state.status.watersensorState == 'normal' + ? Assets.waterLeakNormal + : Assets.waterLeakDetected, + onTap: () {}, + status: state.status.watersensorState == 'normal', + textColor: state.status.watersensorState == 'normal' + ? ColorsManager.blackColor + : ColorsManager.red, + ), + IconNameStatusContainer( + isFullIcon: false, + name: 'Records', + icon: Assets.records, + onTap: () { + context + .read() + .add(FetchWaterLeakReportsEvent( + deviceId: deviceId, + code: 'watersensor_state', + from: DateTime.now() + .subtract(const Duration(days: 30)) + .millisecondsSinceEpoch, + to: DateTime.now().millisecondsSinceEpoch, + )); + }, + status: false, + textColor: ColorsManager.blackColor, + ), + IconNameStatusContainer( + isFullIcon: false, + name: 'Automation Record', + icon: Assets.automationRecords, + onTap: () {}, + status: false, + textColor: ColorsManager.blackColor, + ), + IconNameStatusContainer( + isFullIcon: false, + name: 'Notifications\nSettings', + icon: Assets.mainDoorNotifi, + onTap: () { + showDialog( + context: context, + builder: (context) => const WaterLeakNotificationDialog(), + ); + }, + status: false, + textColor: ColorsManager.blackColor, + paddingAmount: 14, + ), + ], + ); + } else if (state is WaterLeakReportsLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is WaterLeakReportsLoadedState) { + return ReportsTable( + report: state.deviceReport, + hideValueShowDescription: true, + waterLeak: true, + onRowTap: (index) {}, + onClose: () { + context + .read() + .add(FetchWaterLeakStatusEvent(deviceId)); + }, + ); + } else if (state is WaterLeakReportsFailedState) { + return Center(child: Text('Error: ${state.error}')); + } else if (state is WaterLeakErrorState) { + return Center(child: Text('Error: ${state.message}')); + } else { + return const Center(child: Text('No data available')); + } + }, + ), + ); + } +} diff --git a/lib/pages/device_managment/water_leak/widgets/water_leak_notifi_dialog.dart b/lib/pages/device_managment/water_leak/widgets/water_leak_notifi_dialog.dart new file mode 100644 index 00000000..0c2ae6ab --- /dev/null +++ b/lib/pages/device_managment/water_leak/widgets/water_leak_notifi_dialog.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class WaterLeakNotificationDialog extends StatelessWidget { + const WaterLeakNotificationDialog({super.key}); + + @override + Widget build(BuildContext context) { + return Dialog( + backgroundColor: Colors.white, + insetPadding: const EdgeInsets.all(20), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: SizedBox( + width: 400, + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(), + Text( + 'Notification Settings', + 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(); + }, + ), + ), + ], + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ToggleWidget( + value: true, + code: 'notification', + deviceId: '', + label: 'Low Battery', + onChange: (v) {}, + icon: '-1', + ), + ToggleWidget( + value: true, + code: 'notification', + deviceId: '', + label: 'Water Leakage', + onChange: (v) {}, + icon: '-1', + ), + ], + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index b94c48c0..05ef096e 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -13,10 +13,12 @@ class Assets { static const String rightLine = "assets/images/right_line.png"; static const String google = "assets/images/google.svg"; static const String facebook = "assets/images/facebook.svg"; - static const String invisiblePassword = "assets/images/Password_invisible.svg"; + static const String invisiblePassword = + "assets/images/Password_invisible.svg"; static const String visiblePassword = "assets/images/password_visible.svg"; static const String accessIcon = "assets/images/access_icon.svg"; - static const String spaseManagementIcon = "assets/images/spase_management_icon.svg"; + static const String spaseManagementIcon = + "assets/images/spase_management_icon.svg"; static const String devicesIcon = "assets/images/devices_icon.svg"; static const String moveinIcon = "assets/images/movein_icon.svg"; static const String constructionIcon = "assets/images/construction_icon.svg"; @@ -29,13 +31,15 @@ class Assets { static const String emptyTable = "assets/images/empty_table.svg"; // General assets - static const String motionlessDetection = "assets/icons/motionless_detection.svg"; + static const String motionlessDetection = + "assets/icons/motionless_detection.svg"; static const String acHeating = "assets/icons/ac_heating.svg"; static const String acPowerOff = "assets/icons/ac_power_off.svg"; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg"; static const String resetOff = "assets/icons/reset_off.svg"; - static const String sensitivityOperationIcon = "assets/icons/sesitivity_operation_icon.svg"; + static const String sensitivityOperationIcon = + "assets/icons/sesitivity_operation_icon.svg"; static const String motionDetection = "assets/icons/motion_detection.svg"; static const String freezing = "assets/icons/freezing.svg"; static const String indicator = "assets/icons/indicator.svg"; @@ -56,35 +60,56 @@ class Assets { static const String celsiusDegrees = "assets/icons/celsius_degrees.svg"; static const String masterState = "assets/icons/master_state.svg"; static const String acPower = "assets/icons/ac_power.svg"; - static const String farDetectionFunction = "assets/icons/far_detection_function.svg"; + static const String farDetectionFunction = + "assets/icons/far_detection_function.svg"; static const String nobodyTime = "assets/icons/nobody_time.svg"; // Automation functions - static const String tempPasswordUnlock = "assets/icons/automation_functions/temp_password_unlock.svg"; - static const String doorlockNormalOpen = "assets/icons/automation_functions/doorlock_normal_open.svg"; - static const String doorbell = "assets/icons/automation_functions/doorbell.svg"; - static const String remoteUnlockViaApp = "assets/icons/automation_functions/remote_unlock_via_app.svg"; - static const String doubleLock = "assets/icons/automation_functions/double_lock.svg"; - static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg"; - static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg"; - static const String presenceState = "assets/icons/automation_functions/presence_state.svg"; - static const String currentTemp = "assets/icons/automation_functions/current_temp.svg"; - static const String presence = "assets/icons/automation_functions/presence.svg"; - static const String residualElectricity = "assets/icons/automation_functions/residual_electricity.svg"; - static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg"; - static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg"; - static const String remoteUnlockRequest = "assets/icons/automation_functions/remote_unlock_req.svg"; - static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg"; + static const String tempPasswordUnlock = + "assets/icons/automation_functions/temp_password_unlock.svg"; + static const String doorlockNormalOpen = + "assets/icons/automation_functions/doorlock_normal_open.svg"; + static const String doorbell = + "assets/icons/automation_functions/doorbell.svg"; + static const String remoteUnlockViaApp = + "assets/icons/automation_functions/remote_unlock_via_app.svg"; + static const String doubleLock = + "assets/icons/automation_functions/double_lock.svg"; + static const String selfTestResult = + "assets/icons/automation_functions/self_test_result.svg"; + static const String lockAlarm = + "assets/icons/automation_functions/lock_alarm.svg"; + static const String presenceState = + "assets/icons/automation_functions/presence_state.svg"; + static const String currentTemp = + "assets/icons/automation_functions/current_temp.svg"; + static const String presence = + "assets/icons/automation_functions/presence.svg"; + static const String residualElectricity = + "assets/icons/automation_functions/residual_electricity.svg"; + static const String hijackAlarm = + "assets/icons/automation_functions/hijack_alarm.svg"; + static const String passwordUnlock = + "assets/icons/automation_functions/password_unlock.svg"; + static const String remoteUnlockRequest = + "assets/icons/automation_functions/remote_unlock_req.svg"; + static const String cardUnlock = + "assets/icons/automation_functions/card_unlock.svg"; static const String motion = "assets/icons/automation_functions/motion.svg"; - static const String fingerprintUnlock = "assets/icons/automation_functions/fingerprint_unlock.svg"; + static const String fingerprintUnlock = + "assets/icons/automation_functions/fingerprint_unlock.svg"; // Presence Sensor Assets static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; - static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg"; + static const String sensorPresenceIcon = + "assets/icons/sensor_presence_ic.svg"; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; - static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg"; - static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg"; - static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg"; + static const String illuminanceRecordIcon = + "assets/icons/illuminance_record_ic.svg"; + static const String presenceRecordIcon = + "assets/icons/presence_record_ic.svg"; + static const String helpDescriptionIcon = + "assets/icons/help_description_ic.svg"; static const String lightPulp = "assets/icons/light_pulb.svg"; static const String acDevice = "assets/icons/ac_device.svg"; @@ -155,4 +180,12 @@ class Assets { static const String closedDoor = 'assets/icons/closed_door.svg'; static const String doorDelay = 'assets/icons/door_delay.svg'; static const String records = 'assets/icons/records.svg'; + //assets/icons/water_leak_normal.svg + static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg'; + //assets/icons/water_leak_detected.svg + static const String waterLeakDetected = + 'assets/icons/water_leak_detected.svg'; + + //assets/icons/automation_records.svg + static const String automationRecords = 'assets/icons/automation_records.svg'; } From d05328e99876319a166247987ca70aad6e97174f Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Mon, 7 Oct 2024 22:53:18 +0300 Subject: [PATCH 60/65] Bug fixes --- .../shared/device_batch_control_dialog.dart | 23 ++++++++++++++----- .../shared/device_control_dialog.dart | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/pages/device_managment/shared/device_batch_control_dialog.dart b/lib/pages/device_managment/shared/device_batch_control_dialog.dart index 84d822e6..5076ef76 100644 --- a/lib/pages/device_managment/shared/device_batch_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_batch_control_dialog.dart @@ -7,8 +7,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/helper/route_cont import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class DeviceBatchControlDialog extends StatelessWidget - with RouteControlsBasedCode { +class DeviceBatchControlDialog extends StatelessWidget with RouteControlsBasedCode { final List devices; const DeviceBatchControlDialog({super.key, required this.devices}); @@ -110,11 +109,11 @@ String getBatchDialogName(AllDevicesModel device) { case '1G': return "Smart Light Switch"; case '2G': - return "2Gang Light"; + return "Smart Light Switch"; case '3G': - return "Living Room"; + return "Smart Light Switch"; case 'GW': - return "GateWay"; + return "Gateway"; case 'DL': return "Door Lock"; case 'WPS': @@ -124,9 +123,21 @@ String getBatchDialogName(AllDevicesModel device) { case 'CUR': return "Smart Curtains"; case 'WH': - return "Smart Water Hater"; + return "Smart Water Heater"; case 'AC': return "Smart AC"; + case 'DS': + return "Door / Window Sensor"; + case '1GT': + return "Touch Switch"; + case '2GT': + return "Touch Switch"; + case '3GT': + return "Touch Switch"; + case 'GD': + return "Garage Door Opener"; + case 'WL': + return "Water Leak Sensor"; default: return device.categoryName ?? 'Device Control'; } diff --git a/lib/pages/device_managment/shared/device_control_dialog.dart b/lib/pages/device_managment/shared/device_control_dialog.dart index 6a45ce18..14878a46 100644 --- a/lib/pages/device_managment/shared/device_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_control_dialog.dart @@ -31,7 +31,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode { children: [ const SizedBox(), Text( - device.categoryName ?? 'Device Control', + device.productName ?? 'Device Control', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 22, From 813f2f2693d1f8cdc757db73e7f2c668925350dc Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Tue, 8 Oct 2024 01:56:09 +0300 Subject: [PATCH 61/65] Fixed design issues --- lib/pages/common/custom_table.dart | 38 ++++++++------ .../common/text_field/custom_text_field.dart | 52 ++++++++++--------- .../widgets/device_managment_body.dart | 52 +++++++------------ .../widgets/device_search_filters.dart | 26 +++++++--- .../shared/batch_control/firmware_update.dart | 32 ++++++------ .../shared/device_controls_container.dart | 9 +++- lib/utils/color_manager.dart | 3 +- 7 files changed, 114 insertions(+), 98 deletions(-) diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index 02f4e7ad..1de04c02 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -53,11 +53,26 @@ class _DynamicTableState extends State { @override void didUpdateWidget(DynamicTable oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.data.length != widget.data.length) { + if (!_compareListOfLists(oldWidget.data, widget.data)) { _initializeSelection(); } } + bool _compareListOfLists(List> oldList, List> newList) { + // Check if the old and new lists are the same + if (oldList.length != newList.length) return false; + + for (int i = 0; i < oldList.length; i++) { + if (oldList[i].length != newList[i].length) return false; + + for (int j = 0; j < oldList[i].length; j++) { + if (oldList[i][j] != newList[i][j]) return false; + } + } + + return true; + } + void _initializeSelection() { _selectedRows = List.filled(widget.data.length, false); _selectAll = false; @@ -90,13 +105,11 @@ class _DynamicTableState extends State { child: Column( children: [ Container( - decoration: widget.headerDecoration ?? - BoxDecoration(color: Colors.grey[200]), + decoration: widget.headerDecoration ?? BoxDecoration(color: Colors.grey[200]), child: Row( children: [ if (widget.withCheckBox) _buildSelectAllCheckbox(), - ...widget.headers - .map((header) => _buildTableHeaderCell(header)), + ...widget.headers.map((header) => _buildTableHeaderCell(header)), ], ), ), @@ -123,8 +136,7 @@ class _DynamicTableState extends State { style: Theme.of(context) .textTheme .bodySmall! - .copyWith( - color: ColorsManager.grayColor), + .copyWith(color: ColorsManager.grayColor), ) ], ), @@ -144,11 +156,9 @@ class _DynamicTableState extends State { return Row( children: [ if (widget.withCheckBox) - _buildRowCheckbox( - index, widget.size.height * 0.10), - ...row.map((cell) => _buildTableCell( - cell.toString(), - widget.size.height * 0.10)), + _buildRowCheckbox(index, widget.size.height * 0.10), + ...row.map((cell) => + _buildTableCell(cell.toString(), widget.size.height * 0.10)), ], ); }, @@ -173,9 +183,7 @@ class _DynamicTableState extends State { ), child: Checkbox( value: _selectAll, - onChanged: widget.withSelectAll && widget.data.isNotEmpty - ? _toggleSelectAll - : null, + onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null, ), ); } diff --git a/lib/pages/common/text_field/custom_text_field.dart b/lib/pages/common/text_field/custom_text_field.dart index f54d3991..c85e911d 100644 --- a/lib/pages/common/text_field/custom_text_field.dart +++ b/lib/pages/common/text_field/custom_text_field.dart @@ -2,20 +2,21 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class StatefulTextField extends StatefulWidget { - const StatefulTextField({ - super.key, - required this.title, - this.hintText = 'Please enter', - required this.width, - this.elevation = 0, - required this.controller, - }); + const StatefulTextField( + {super.key, + required this.title, + this.hintText = 'Please enter', + required this.width, + this.elevation = 0, + required this.controller, + this.onSubmitted}); final String title; final String hintText; final double width; final double elevation; final TextEditingController controller; + final Function? onSubmitted; @override State createState() => _StatefulTextFieldState(); @@ -25,30 +26,31 @@ class _StatefulTextFieldState extends State { @override Widget build(BuildContext context) { return CustomTextField( - title: widget.title, - controller: widget.controller, - hintText: widget.hintText, - width: widget.width, - elevation: widget.elevation, - ); + title: widget.title, + controller: widget.controller, + hintText: widget.hintText, + width: widget.width, + elevation: widget.elevation, + onSubmittedFun: widget.onSubmitted); } } class CustomTextField extends StatelessWidget { - const CustomTextField({ - super.key, - required this.title, - required this.controller, - this.hintText = 'Please enter', - required this.width, - this.elevation = 0, - }); + const CustomTextField( + {super.key, + required this.title, + required this.controller, + this.hintText = 'Please enter', + required this.width, + this.elevation = 0, + this.onSubmittedFun}); final String title; final TextEditingController controller; final String hintText; final double width; final double elevation; + final Function? onSubmittedFun; @override Widget build(BuildContext context) { @@ -81,10 +83,12 @@ class CustomTextField extends StatelessWidget { decoration: InputDecoration( hintText: hintText, hintStyle: const TextStyle(fontSize: 12), - contentPadding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), border: InputBorder.none, ), + onFieldSubmitted: (_) { + onSubmittedFun!(); + }, ), ), ), diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index 242c8d5e..21ed882c 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -37,8 +37,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { offlineCount = state.offlineCount; lowBatteryCount = state.lowBatteryCount; isControlButtonEnabled = state.isControlButtonEnabled; - selectedDevices = state.selectedDevice ?? - context.read().selectedDevices; + selectedDevices = + state.selectedDevice ?? context.read().selectedDevices; } else if (state is DeviceManagementFiltered) { devicesToShow = state.filteredDevices; selectedIndex = state.selectedIndex; @@ -46,8 +46,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { offlineCount = state.offlineCount; lowBatteryCount = state.lowBatteryCount; isControlButtonEnabled = state.isControlButtonEnabled; - selectedDevices = state.selectedDevice ?? - context.read().selectedDevices; + selectedDevices = + state.selectedDevice ?? context.read().selectedDevices; } else if (state is DeviceManagementInitial) { devicesToShow = []; selectedIndex = 0; @@ -61,15 +61,13 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { 'Low Battery ($lowBatteryCount)', ]; - final buttonLabel = - (selectedDevices.length > 1) ? 'Batch Control' : 'Control'; + final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control'; return Column( children: [ Container( - padding: isLargeScreenSize(context) - ? const EdgeInsets.all(30) - : const EdgeInsets.all(15), + padding: + isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -78,9 +76,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { tabs: tabs, selectedIndex: selectedIndex, onTabChanged: (index) { - context - .read() - .add(SelectedFilterChanged(index)); + context.read().add(SelectedFilterChanged(index)); }, ), const SizedBox(height: 20), @@ -102,14 +98,12 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { ), ); } else if (selectedDevices.length > 1) { - final productTypes = selectedDevices - .map((device) => device.productType) - .toSet(); + final productTypes = + selectedDevices.map((device) => device.productType).toSet(); if (productTypes.length == 1) { showDialog( context: context, - builder: (context) => - DeviceBatchControlDialog( + builder: (context) => DeviceBatchControlDialog( devices: selectedDevices, ), ); @@ -123,9 +117,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { textAlign: TextAlign.center, style: TextStyle( fontSize: 12, - color: isControlButtonEnabled - ? Colors.white - : Colors.grey, + color: isControlButtonEnabled ? Colors.white : Colors.grey, ), ), ), @@ -144,9 +136,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { cellDecoration: containerDecoration, onRowSelected: (index, isSelected, row) { final selectedDevice = devicesToShow[index]; - context - .read() - .add(SelectDevice(selectedDevice)); + context.read().add(SelectDevice(selectedDevice)); }, withCheckBox: true, size: context.screenSize, @@ -169,20 +159,16 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { device.uuid ?? '', device.unit?.name ?? '', device.room?.name ?? '', - device.batteryLevel != null - ? '${device.batteryLevel}%' - : '-', - formatDateTime(DateTime.fromMillisecondsSinceEpoch( - (device.createTime ?? 0) * 1000)), + device.batteryLevel != null ? '${device.batteryLevel}%' : '-', + formatDateTime( + DateTime.fromMillisecondsSinceEpoch((device.createTime ?? 0) * 1000)), device.online == true ? 'Online' : 'Offline', - formatDateTime(DateTime.fromMillisecondsSinceEpoch( - (device.updateTime ?? 0) * 1000)), + formatDateTime( + DateTime.fromMillisecondsSinceEpoch((device.updateTime ?? 0) * 1000)), ]; }).toList(), onSelectionChanged: (selectedRows) { - context - .read() - .add(UpdateSelection(selectedRows)); + context.read().add(UpdateSelection(selectedRows)); }, initialSelectedIds: context .read() diff --git a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart index 51215c84..4690a810 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_search_filters.dart @@ -12,8 +12,7 @@ class DeviceSearchFilters extends StatefulWidget { State createState() => _DeviceSearchFiltersState(); } -class _DeviceSearchFiltersState extends State - with HelperResponsiveLayout { +class _DeviceSearchFiltersState extends State with HelperResponsiveLayout { final TextEditingController communityController = TextEditingController(); final TextEditingController unitNameController = TextEditingController(); final TextEditingController productNameController = TextEditingController(); @@ -35,8 +34,7 @@ class _DeviceSearchFiltersState extends State const SizedBox(width: 20), _buildSearchField("Unit Name", unitNameController, 200), const SizedBox(width: 20), - _buildSearchField( - "Device Name / Product Name", productNameController, 300), + _buildSearchField("Device Name / Product Name", productNameController, 300), const SizedBox(width: 20), _buildSearchResetButtons(), ], @@ -45,22 +43,34 @@ class _DeviceSearchFiltersState extends State spacing: 20, runSpacing: 10, children: [ - _buildSearchField("Community", communityController, 200), + _buildSearchField( + "Community", + communityController, + 200, + ), _buildSearchField("Unit Name", unitNameController, 200), _buildSearchField( - "Device Name / Product Name", productNameController, 300), + "Device Name / Product Name", + productNameController, + 300, + ), _buildSearchResetButtons(), ], ); } - Widget _buildSearchField( - String title, TextEditingController controller, double width) { + Widget _buildSearchField(String title, TextEditingController controller, double width) { return StatefulTextField( title: title, width: width, elevation: 2, controller: controller, + onSubmitted: () { + context.read().add(SearchDevices( + productName: productNameController.text, + unitName: unitNameController.text, + )); + }, ); } diff --git a/lib/pages/device_managment/shared/batch_control/firmware_update.dart b/lib/pages/device_managment/shared/batch_control/firmware_update.dart index 095d3efc..e99ee948 100644 --- a/lib/pages/device_managment/shared/batch_control/firmware_update.dart +++ b/lib/pages/device_managment/shared/batch_control/firmware_update.dart @@ -7,8 +7,7 @@ import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class FirmwareUpdateWidget extends StatefulWidget { - const FirmwareUpdateWidget( - {super.key, required this.deviceId, required this.version}); + const FirmwareUpdateWidget({super.key, required this.deviceId, required this.version}); final String deviceId; final int version; @@ -34,20 +33,23 @@ class _FirmwareUpdateWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - 'Firmware Update', - style: context.textTheme.titleMedium!.copyWith( - fontWeight: FontWeight.bold, - color: ColorsManager.blackColor, - ), + Column( + children: [ + Text( + 'Firmware Update', + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.bold, + color: ColorsManager.blackColor, + ), + ), + Text( + 'Are you sure?', + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.grayColor, + ), + ), + ], ), - Text( - 'Are you sure?', - style: context.textTheme.bodySmall!.copyWith( - color: ColorsManager.grayColor, - ), - ), - const SizedBox(height: 16), Row( children: [ Flexible( diff --git a/lib/pages/device_managment/shared/device_controls_container.dart b/lib/pages/device_managment/shared/device_controls_container.dart index b60a958f..9de6cdb8 100644 --- a/lib/pages/device_managment/shared/device_controls_container.dart +++ b/lib/pages/device_managment/shared/device_controls_container.dart @@ -11,7 +11,14 @@ class DeviceControlsContainer extends StatelessWidget { decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), color: ColorsManager.greyColor.withOpacity(0.2), - border: Border.all(color: ColorsManager.boxDivider), + // border: Border.all(color: ColorsManager.boxDivider), + // boxShadow: [ + // BoxShadow( + // color: ColorsManager.blackColor.withOpacity(0.05), + // blurRadius: 6.0, + // offset: const Offset(0, 5), + // spreadRadius: 0) + // ], ), padding: const EdgeInsets.all(12), child: child, diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index b62889d0..1eaf8845 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -5,8 +5,7 @@ abstract class ColorsManager { static const Color switchOffColor = Color(0x7F8D99AE); static const Color primaryColor = Color(0xFF0030CB); //023DFE static const Color secondaryTextColor = Color(0xFF848484); - static Color primaryColorWithOpacity = - const Color(0xFF023DFE).withOpacity(0.6); + static Color primaryColorWithOpacity = const Color(0xFF023DFE).withOpacity(0.6); static const Color whiteColors = Colors.white; static const Color secondaryColor = Color(0xFF023DFE); static const Color onSecondaryColor = Color(0xFF023DFE); From c06f3d5a586860c1bccd5433d982519d68cd914b Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Tue, 8 Oct 2024 02:19:59 +0300 Subject: [PATCH 62/65] Changed the color of the device control container --- .../device_managment/shared/device_controls_container.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pages/device_managment/shared/device_controls_container.dart b/lib/pages/device_managment/shared/device_controls_container.dart index 9de6cdb8..158b43cf 100644 --- a/lib/pages/device_managment/shared/device_controls_container.dart +++ b/lib/pages/device_managment/shared/device_controls_container.dart @@ -10,8 +10,7 @@ class DeviceControlsContainer extends StatelessWidget { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), - color: ColorsManager.greyColor.withOpacity(0.2), - // border: Border.all(color: ColorsManager.boxDivider), + color: ColorsManager.grayColor.withOpacity(0.2), // boxShadow: [ // BoxShadow( // color: ColorsManager.blackColor.withOpacity(0.05), From 66f6b1cba9770a2a20bd355872de6e8ff0d19470 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 8 Oct 2024 11:48:35 +0300 Subject: [PATCH 63/65] fix bugs --- assets/icons/1gang.svg | 5 ++ assets/icons/2gang.svg | 7 ++ lib/pages/common/curtain_toggle.dart | 10 +-- lib/pages/common/custom_table.dart | 36 ++++---- .../ac/view/ac_device_batch_control.dart | 19 ++--- .../ac/view/ac_device_control.dart | 12 +-- .../batch_control_list/batch_ac_mode.dart | 29 +++---- .../batch_current_temp.dart | 76 ++++++++--------- .../batch_control_list/batch_fan_speed.dart | 33 +++----- .../ac/view/control_list/ac_mode.dart | 29 +++---- .../ac/view/control_list/ac_toggle.dart | 10 +-- .../ac/view/control_list/current_temp.dart | 27 ++---- .../ac/view/control_list/fan_speed.dart | 32 +++---- .../bloc/device_managment_bloc.dart | 71 +++++----------- .../widgets/device_managment_body.dart | 36 +++----- .../ceiling_sensor/bloc/bloc.dart | 41 ++++----- .../widgets/schedule__garage_table.dart | 48 ++++------- .../gateway/bloc/gate_way_bloc.dart | 9 +- .../gateway/view/gateway_view.dart | 2 +- .../shared/device_controls_container.dart | 8 +- .../shared/toggle_widget.dart | 9 +- .../visitor_password/model/device_model.dart | 12 ++- lib/services/devices_mang_api.dart | 28 ++----- lib/utils/constants/assets.dart | 84 +++++++------------ lib/utils/enum/device_types.dart | 7 ++ 25 files changed, 258 insertions(+), 422 deletions(-) create mode 100644 assets/icons/1gang.svg create mode 100644 assets/icons/2gang.svg diff --git a/assets/icons/1gang.svg b/assets/icons/1gang.svg new file mode 100644 index 00000000..647dc350 --- /dev/null +++ b/assets/icons/1gang.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/2gang.svg b/assets/icons/2gang.svg new file mode 100644 index 00000000..6cfe191b --- /dev/null +++ b/assets/icons/2gang.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/pages/common/curtain_toggle.dart b/lib/pages/common/curtain_toggle.dart index a44822c3..305ede03 100644 --- a/lib/pages/common/curtain_toggle.dart +++ b/lib/pages/common/curtain_toggle.dart @@ -1,6 +1,6 @@ import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -22,13 +22,7 @@ class CurtainToggle extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: ColorsManager.greyColor.withOpacity(0.2), - border: Border.all(color: ColorsManager.boxDivider), - ), - padding: const EdgeInsets.all(16), + return DeviceControlsContainer( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/lib/pages/common/custom_table.dart b/lib/pages/common/custom_table.dart index 1de04c02..5b6692ae 100644 --- a/lib/pages/common/custom_table.dart +++ b/lib/pages/common/custom_table.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -78,20 +80,22 @@ class _DynamicTableState extends State { _selectAll = false; } - void _toggleSelectAll(bool? value) { - setState(() { - _selectAll = value ?? false; - _selectedRows = List.filled(widget.data.length, _selectAll); - }); - widget.onSelectionChanged?.call(_selectedRows); - } - void _toggleRowSelection(int index) { setState(() { _selectedRows[index] = !_selectedRows[index]; _selectAll = _selectedRows.every((isSelected) => isSelected); }); widget.onSelectionChanged?.call(_selectedRows); + context.read().add(UpdateSelection(_selectedRows)); + } + + void _toggleSelectAll(bool? value) { + setState(() { + _selectAll = value ?? false; + _selectedRows = List.filled(widget.data.length, _selectAll); + }); + widget.onSelectionChanged?.call(_selectedRows); + context.read().add(UpdateSelection(_selectedRows)); } @override @@ -130,13 +134,9 @@ class _DynamicTableState extends State { ), Text( // no password - widget.tableName == 'AccessManagement' - ? 'No Password ' - : 'No Devices', - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(color: ColorsManager.grayColor), + widget.tableName == 'AccessManagement' ? 'No Password ' : 'No Devices', + style: + Theme.of(context).textTheme.bodySmall!.copyWith(color: ColorsManager.grayColor), ) ], ), @@ -155,10 +155,8 @@ class _DynamicTableState extends State { final row = widget.data[index]; return Row( children: [ - if (widget.withCheckBox) - _buildRowCheckbox(index, widget.size.height * 0.10), - ...row.map((cell) => - _buildTableCell(cell.toString(), widget.size.height * 0.10)), + if (widget.withCheckBox) _buildRowCheckbox(index, widget.size.height * 0.10), + ...row.map((cell) => _buildTableCell(cell.toString(), widget.size.height * 0.10)), ], ); }, diff --git a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart index 1930e9c4..2da394c7 100644 --- a/lib/pages/device_managment/ac/view/ac_device_batch_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_batch_control.dart @@ -15,8 +15,7 @@ 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'; -class AcDeviceBatchControlView extends StatelessWidget - with HelperResponsiveLayout { +class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayout { const AcDeviceBatchControlView({super.key, required this.devicesIds}); final List devicesIds; @@ -27,8 +26,7 @@ class AcDeviceBatchControlView extends StatelessWidget final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); return BlocProvider( - create: (context) => AcBloc(deviceId: devicesIds.first) - ..add(AcFetchBatchStatusEvent(devicesIds)), + create: (context) => AcBloc(deviceId: devicesIds.first)..add(AcFetchBatchStatusEvent(devicesIds)), child: BlocBuilder( builder: (context, state) { if (state is ACStatusLoaded) { @@ -66,6 +64,7 @@ class AcDeviceBatchControlView extends StatelessWidget tempSet: state.status.tempSet, code: 'temp_set', devicesIds: devicesIds, + isBatch: true, ), BatchAcMode( value: state.status.acMode, @@ -99,8 +98,7 @@ class AcDeviceBatchControlView extends StatelessWidget ), Text( 'h', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.blackColor), + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), ), Text( '30', @@ -109,9 +107,7 @@ class AcDeviceBatchControlView extends StatelessWidget fontWeight: FontWeight.bold, ), ), - Text('m', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.blackColor)), + Text('m', style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor)), IconButton( onPressed: () {}, icon: const Icon( @@ -133,7 +129,7 @@ class AcDeviceBatchControlView extends StatelessWidget code: 'child_lock', value: state.status.childLock, label: 'Child Lock', - icon: state.status.childLock ? Assets.unlock : Assets.acLock, + icon: state.status.childLock ? Assets.acLock : Assets.unlock, onChange: (value) { context.read().add(AcBatchControlEvent( devicesIds: devicesIds, @@ -147,8 +143,7 @@ class AcDeviceBatchControlView extends StatelessWidget callFactoryReset: () { context.read().add(AcFactoryResetEvent( deviceId: state.status.uuid, - factoryResetModel: - FactoryResetModel(devicesUuid: devicesIds), + factoryResetModel: FactoryResetModel(devicesUuid: devicesIds), )); }, ), diff --git a/lib/pages/device_managment/ac/view/ac_device_control.dart b/lib/pages/device_managment/ac/view/ac_device_control.dart index 3c383854..37d3a402 100644 --- a/lib/pages/device_managment/ac/view/ac_device_control.dart +++ b/lib/pages/device_managment/ac/view/ac_device_control.dart @@ -24,8 +24,7 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); return BlocProvider( - create: (context) => AcBloc(deviceId: device.uuid!) - ..add(AcFetchDeviceStatusEvent(device.uuid!)), + create: (context) => AcBloc(deviceId: device.uuid!)..add(AcFetchDeviceStatusEvent(device.uuid!)), child: BlocBuilder( builder: (context, state) { if (state is ACStatusLoaded) { @@ -98,8 +97,7 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { ), Text( 'h', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.blackColor), + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor), ), Text( '30', @@ -108,9 +106,7 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { fontWeight: FontWeight.bold, ), ), - Text('m', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.blackColor)), + Text('m', style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blackColor)), IconButton( onPressed: () {}, icon: const Icon( @@ -132,7 +128,7 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout { code: 'child_lock', value: state.status.childLock, label: 'Child Lock', - icon: state.status.childLock ? Assets.unlock : Assets.acLock, + icon: state.status.childLock ? Assets.acLock : Assets.unlock, onChange: (value) { context.read().add( AcControlEvent( diff --git a/lib/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart b/lib/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart index 8b601ac2..60d48256 100644 --- a/lib/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart +++ b/lib/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; +import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; class BatchAcMode extends StatelessWidget { const BatchAcMode({ @@ -21,30 +22,20 @@ class BatchAcMode extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: ColorsManager.greyColor.withOpacity(0.2), - border: Border.all(color: ColorsManager.boxDivider), - ), - padding: const EdgeInsets.all(16), + return DeviceControlsContainer( child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - _buildIconContainer(context, TempModes.cold, Assets.freezing, - value == TempModes.cold), - _buildIconContainer( - context, TempModes.hot, Assets.acSun, value == TempModes.hot), - _buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, - value == TempModes.wind), + _buildIconContainer(context, TempModes.cold, Assets.freezing, value == TempModes.cold), + _buildIconContainer(context, TempModes.hot, Assets.acSun, value == TempModes.hot), + _buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, value == TempModes.wind), ], ), ); } - Widget _buildIconContainer( - BuildContext context, TempModes mode, String assetPath, bool isSelected) { + Widget _buildIconContainer(BuildContext context, TempModes mode, String assetPath, bool isSelected) { return Flexible( child: GestureDetector( onTap: () { diff --git a/lib/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart b/lib/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart index ba5ea639..be7441df 100644 --- a/lib/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart +++ b/lib/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart @@ -1,11 +1,13 @@ import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; import 'package:syncrow_web/pages/device_managment/shared/celciuse_symbol.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; class BatchCurrentTemp extends StatefulWidget { const BatchCurrentTemp({ @@ -14,12 +16,14 @@ class BatchCurrentTemp extends StatefulWidget { required this.devicesIds, required this.currentTemp, required this.tempSet, + this.isBatch, }); final String code; final List devicesIds; final int currentTemp; final int tempSet; + final bool? isBatch; @override State createState() => _CurrentTempState(); @@ -67,49 +71,39 @@ class _CurrentTempState extends State { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: ColorsManager.greyColor.withOpacity(0.2), - border: Border.all(color: ColorsManager.boxDivider), - ), - padding: const EdgeInsets.all(16), + return DeviceControlsContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Current Temperature', - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(color: Colors.grey), - ), - const SizedBox( - height: 5, - ), - Row( - children: [ - Text( - (widget.currentTemp > 99 - ? widget.currentTemp / 10 - : widget.currentTemp) - .toString(), - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(color: Colors.grey), - ), - const CelsiusSymbol( - color: Colors.grey, - ) - ], - ), - ], - ), + widget.isBatch == true + ? Text( + 'Set Temperature', + style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey), + ) + : Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Current Temperature', + style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey), + ), + const SizedBox( + height: 5, + ), + Row( + children: [ + Text( + (widget.currentTemp > 99 ? widget.currentTemp / 10 : widget.currentTemp).toString(), + style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey), + ), + const CelsiusSymbol( + color: Colors.grey, + ) + ], + ), + ], + ), const Spacer(), IncrementDecrementWidget( value: _adjustedValue.toString(), diff --git a/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart b/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart index 11882473..ba49047a 100644 --- a/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart +++ b/lib/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; +import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; class BatchFanSpeedControl extends StatelessWidget { const BatchFanSpeedControl({ @@ -21,23 +22,16 @@ class BatchFanSpeedControl extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: ColorsManager.greyColor.withOpacity(0.2), - border: Border.all(color: ColorsManager.boxDivider), - ), - padding: const EdgeInsets.all(8), + return DeviceControlsContainer( + padding: 8, child: Column( children: [ Wrap( runSpacing: 8, spacing: 8, children: [ - _buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, - value == FanSpeeds.auto), - _buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, - value == FanSpeeds.low), + _buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, value == FanSpeeds.auto), + _buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, value == FanSpeeds.low), ], ), const SizedBox(height: 8), @@ -45,10 +39,8 @@ class BatchFanSpeedControl extends StatelessWidget { runSpacing: 8, spacing: 8, children: [ - _buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, - value == FanSpeeds.middle), - _buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, - value == FanSpeeds.high), + _buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, value == FanSpeeds.middle), + _buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, value == FanSpeeds.high), ], ) ], @@ -56,8 +48,7 @@ class BatchFanSpeedControl extends StatelessWidget { ); } - Widget _buildIconContainer(BuildContext context, FanSpeeds speed, - String assetPath, bool isSelected) { + Widget _buildIconContainer(BuildContext context, FanSpeeds speed, String assetPath, bool isSelected) { return GestureDetector( onTap: () { context.read().add( diff --git a/lib/pages/device_managment/ac/view/control_list/ac_mode.dart b/lib/pages/device_managment/ac/view/control_list/ac_mode.dart index 65c7ae0a..c6ffc052 100644 --- a/lib/pages/device_managment/ac/view/control_list/ac_mode.dart +++ b/lib/pages/device_managment/ac/view/control_list/ac_mode.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; +import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; class AcMode extends StatelessWidget { const AcMode({ @@ -21,30 +22,20 @@ class AcMode extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: ColorsManager.greyColor.withOpacity(0.2), - border: Border.all(color: ColorsManager.boxDivider), - ), - padding: const EdgeInsets.all(16), + return DeviceControlsContainer( child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - _buildIconContainer(context, TempModes.cold, Assets.freezing, - value == TempModes.cold), - _buildIconContainer( - context, TempModes.hot, Assets.acSun, value == TempModes.hot), - _buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, - value == TempModes.wind), + _buildIconContainer(context, TempModes.cold, Assets.freezing, value == TempModes.cold), + _buildIconContainer(context, TempModes.hot, Assets.acSun, value == TempModes.hot), + _buildIconContainer(context, TempModes.wind, Assets.acAirConditioner, value == TempModes.wind), ], ), ); } - Widget _buildIconContainer( - BuildContext context, TempModes mode, String assetPath, bool isSelected) { + Widget _buildIconContainer(BuildContext context, TempModes mode, String assetPath, bool isSelected) { return Flexible( child: GestureDetector( onTap: () { diff --git a/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart b/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart index e709a6b4..4e81ec09 100644 --- a/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart +++ b/lib/pages/device_managment/ac/view/control_list/ac_toggle.dart @@ -1,9 +1,9 @@ import 'package:flutter/cupertino.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/device_managment/ac/bloc/ac_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -25,13 +25,7 @@ class AcToggle extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: ColorsManager.greyColor.withOpacity(0.2), - border: Border.all(color: ColorsManager.boxDivider), - ), - padding: const EdgeInsets.all(16), + return DeviceControlsContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/pages/device_managment/ac/view/control_list/current_temp.dart b/lib/pages/device_managment/ac/view/control_list/current_temp.dart index 0327e357..7618846c 100644 --- a/lib/pages/device_managment/ac/view/control_list/current_temp.dart +++ b/lib/pages/device_managment/ac/view/control_list/current_temp.dart @@ -1,11 +1,13 @@ import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; import 'package:syncrow_web/pages/device_managment/shared/celciuse_symbol.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; class CurrentTemp extends StatefulWidget { const CurrentTemp({ @@ -67,13 +69,7 @@ class _CurrentTempState extends State { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: ColorsManager.greyColor.withOpacity(0.2), - border: Border.all(color: ColorsManager.boxDivider), - ), - padding: const EdgeInsets.all(16), + return DeviceControlsContainer( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -83,10 +79,7 @@ class _CurrentTempState extends State { children: [ Text( 'Current Temperature', - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(color: Colors.grey), + style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey), ), const SizedBox( height: 5, @@ -94,14 +87,8 @@ class _CurrentTempState extends State { Row( children: [ Text( - (widget.currentTemp > 99 - ? widget.currentTemp / 10 - : widget.currentTemp) - .toString(), - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(color: Colors.grey), + (widget.currentTemp > 99 ? widget.currentTemp / 10 : widget.currentTemp).toString(), + style: Theme.of(context).textTheme.bodySmall!.copyWith(color: Colors.grey), ), const CelsiusSymbol( color: Colors.grey, diff --git a/lib/pages/device_managment/ac/view/control_list/fan_speed.dart b/lib/pages/device_managment/ac/view/control_list/fan_speed.dart index 8612c689..952e112b 100644 --- a/lib/pages/device_managment/ac/view/control_list/fan_speed.dart +++ b/lib/pages/device_managment/ac/view/control_list/fan_speed.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; +import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; class FanSpeedControl extends StatelessWidget { const FanSpeedControl({ @@ -21,23 +22,15 @@ class FanSpeedControl extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: ColorsManager.greyColor.withOpacity(0.2), - border: Border.all(color: ColorsManager.boxDivider), - ), - padding: const EdgeInsets.all(8), + return DeviceControlsContainer( child: Column( children: [ Wrap( runSpacing: 8, spacing: 8, children: [ - _buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, - value == FanSpeeds.auto), - _buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, - value == FanSpeeds.low), + _buildIconContainer(context, FanSpeeds.auto, Assets.acFanAuto, value == FanSpeeds.auto), + _buildIconContainer(context, FanSpeeds.low, Assets.acFanLow, value == FanSpeeds.low), ], ), const SizedBox(height: 8), @@ -45,10 +38,8 @@ class FanSpeedControl extends StatelessWidget { runSpacing: 8, spacing: 8, children: [ - _buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, - value == FanSpeeds.middle), - _buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, - value == FanSpeeds.high), + _buildIconContainer(context, FanSpeeds.middle, Assets.acFanMiddle, value == FanSpeeds.middle), + _buildIconContainer(context, FanSpeeds.high, Assets.acFanHigh, value == FanSpeeds.high), ], ) ], @@ -56,8 +47,7 @@ class FanSpeedControl extends StatelessWidget { ); } - Widget _buildIconContainer(BuildContext context, FanSpeeds speed, - String assetPath, bool isSelected) { + Widget _buildIconContainer(BuildContext context, FanSpeeds speed, String assetPath, bool isSelected) { return GestureDetector( onTap: () { context.read().add( diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart index fc066335..5c35f9e9 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart @@ -6,8 +6,7 @@ import 'package:syncrow_web/services/devices_mang_api.dart'; part 'device_managment_event.dart'; part 'device_managment_state.dart'; -class DeviceManagementBloc - extends Bloc { +class DeviceManagementBloc extends Bloc { int _selectedIndex = 0; List _devices = []; int _onlineCount = 0; @@ -28,8 +27,7 @@ class DeviceManagementBloc on(_onUpdateSelection); } - Future _onFetchDevices( - FetchDevices event, Emitter emit) async { + Future _onFetchDevices(FetchDevices event, Emitter emit) async { emit(DeviceManagementLoading()); try { final devices = await DevicesManagementApi().fetchDevices(); @@ -51,8 +49,7 @@ class DeviceManagementBloc } } - void _onFilterDevices( - FilterDevices event, Emitter emit) async { + void _onFilterDevices(FilterDevices event, Emitter emit) async { if (_devices.isNotEmpty) { _filteredDevices = List.from(_devices.where((device) { switch (event.filter) { @@ -83,8 +80,7 @@ class DeviceManagementBloc } } - Future _onResetFilters( - ResetFilters event, Emitter emit) async { + Future _onResetFilters(ResetFilters event, Emitter emit) async { productName = ''; _selectedDevices.clear(); _filteredDevices = List.from(_devices); @@ -100,8 +96,7 @@ class DeviceManagementBloc )); } - void _onResetSelectedDevices( - ResetSelectedDevices event, Emitter emit) { + void _onResetSelectedDevices(ResetSelectedDevices event, Emitter emit) { _selectedDevices.clear(); if (state is DeviceManagementLoaded) { @@ -127,14 +122,12 @@ class DeviceManagementBloc } } - void _onSelectedFilterChanged( - SelectedFilterChanged event, Emitter emit) { + void _onSelectedFilterChanged(SelectedFilterChanged event, Emitter emit) { _selectedIndex = event.selectedIndex; add(FilterDevices(_getFilterFromIndex(_selectedIndex))); } - void _onSelectDevice( - SelectDevice event, Emitter emit) { + void _onSelectDevice(SelectDevice event, Emitter emit) { final selectedUuid = event.selectedDevice.uuid; if (_selectedDevices.any((device) => device.uuid == selectedUuid)) { @@ -145,8 +138,7 @@ class DeviceManagementBloc List clonedSelectedDevices = List.from(_selectedDevices); - bool isControlButtonEnabled = - _checkIfControlButtonEnabled(clonedSelectedDevices); + bool isControlButtonEnabled = _checkIfControlButtonEnabled(clonedSelectedDevices); if (state is DeviceManagementLoaded) { emit(DeviceManagementLoaded( @@ -155,8 +147,7 @@ class DeviceManagementBloc onlineCount: _onlineCount, offlineCount: _offlineCount, lowBatteryCount: _lowBatteryCount, - selectedDevice: - clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null, + selectedDevice: clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null, isControlButtonEnabled: isControlButtonEnabled, )); } else if (state is DeviceManagementFiltered) { @@ -166,15 +157,13 @@ class DeviceManagementBloc onlineCount: _onlineCount, offlineCount: _offlineCount, lowBatteryCount: _lowBatteryCount, - selectedDevice: - clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null, + selectedDevice: clonedSelectedDevices.isNotEmpty ? clonedSelectedDevices : null, isControlButtonEnabled: isControlButtonEnabled, )); } } - void _onUpdateSelection( - UpdateSelection event, Emitter emit) { + void _onUpdateSelection(UpdateSelection event, Emitter emit) { List selectedDevices = []; List devicesToSelectFrom = []; @@ -217,8 +206,7 @@ class DeviceManagementBloc bool _checkIfControlButtonEnabled(List selectedDevices) { if (selectedDevices.length > 1) { - final productTypes = - selectedDevices.map((device) => device.productType).toSet(); + final productTypes = selectedDevices.map((device) => device.productType).toSet(); return productTypes.length == 1; } else if (selectedDevices.length == 1) { return true; @@ -229,10 +217,7 @@ class DeviceManagementBloc void _calculateDeviceCounts() { _onlineCount = _devices.where((device) => device.online == true).length; _offlineCount = _devices.where((device) => device.online == false).length; - _lowBatteryCount = _devices - .where((device) => - device.batteryLevel != null && device.batteryLevel! < 20) - .length; + _lowBatteryCount = _devices.where((device) => device.batteryLevel != null && device.batteryLevel! < 20).length; } String _getFilterFromIndex(int index) { @@ -248,8 +233,7 @@ class DeviceManagementBloc } } - void _onSearchDevices( - SearchDevices event, Emitter emit) { + void _onSearchDevices(SearchDevices event, Emitter emit) { if ((event.community == null || event.community!.isEmpty) && (event.unitName == null || event.unitName!.isEmpty) && (event.productName == null || event.productName!.isEmpty)) { @@ -263,38 +247,21 @@ class DeviceManagementBloc List devicesToSearch = _filteredDevices; if (devicesToSearch.isNotEmpty) { - _selectedDevices.clear(); - _selectedIndex = _selectedIndex; - final filteredDevices = devicesToSearch.where((device) { final matchesCommunity = event.community == null || event.community!.isEmpty || - (device.room?.name - ?.toLowerCase() - .contains(event.community!.toLowerCase()) ?? - false); + (device.room?.name?.toLowerCase().contains(event.community!.toLowerCase()) ?? false); final matchesUnit = event.unitName == null || event.unitName!.isEmpty || - (device.unit?.name - ?.toLowerCase() - .contains(event.unitName!.toLowerCase()) ?? - false); + (device.unit?.name?.toLowerCase().contains(event.unitName!.toLowerCase()) ?? false); final matchesProductName = event.productName == null || event.productName!.isEmpty || - (device.name - ?.toLowerCase() - .contains(event.productName!.toLowerCase()) ?? - false); + (device.name?.toLowerCase().contains(event.productName!.toLowerCase()) ?? false); final matchesDeviceName = event.productName == null || event.productName!.isEmpty || - (device.categoryName - ?.toLowerCase() - .contains(event.productName!.toLowerCase()) ?? - false); + (device.categoryName?.toLowerCase().contains(event.productName!.toLowerCase()) ?? false); - return matchesCommunity && - matchesUnit && - (matchesProductName || matchesDeviceName); + return matchesCommunity && matchesUnit && (matchesProductName || matchesDeviceName); }).toList(); emit(DeviceManagementFiltered( diff --git a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart index 21ed882c..2787c7b9 100644 --- a/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart +++ b/lib/pages/device_managment/all_devices/widgets/device_managment_body.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/custom_table.dart'; import 'package:syncrow_web/pages/common/filter/filter_widget.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/format_date_time.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -37,8 +37,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { offlineCount = state.offlineCount; lowBatteryCount = state.lowBatteryCount; isControlButtonEnabled = state.isControlButtonEnabled; - selectedDevices = - state.selectedDevice ?? context.read().selectedDevices; + selectedDevices = state.selectedDevice ?? []; } else if (state is DeviceManagementFiltered) { devicesToShow = state.filteredDevices; selectedIndex = state.selectedIndex; @@ -46,14 +45,12 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { offlineCount = state.offlineCount; lowBatteryCount = state.lowBatteryCount; isControlButtonEnabled = state.isControlButtonEnabled; - selectedDevices = - state.selectedDevice ?? context.read().selectedDevices; + selectedDevices = state.selectedDevice ?? []; } else if (state is DeviceManagementInitial) { devicesToShow = []; selectedIndex = 0; isControlButtonEnabled = false; } - final tabs = [ 'All', 'Online ($onlineCount)', @@ -66,8 +63,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { return Column( children: [ Container( - padding: - isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15), + padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -98,8 +94,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { ), ); } else if (selectedDevices.length > 1) { - final productTypes = - selectedDevices.map((device) => device.productType).toSet(); + final productTypes = selectedDevices.map((device) => device.productType).toSet(); if (productTypes.length == 1) { showDialog( context: context, @@ -128,9 +123,7 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { ), Expanded( child: Padding( - padding: isLargeScreenSize(context) - ? const EdgeInsets.all(30) - : const EdgeInsets.all(15), + padding: isLargeScreenSize(context) ? const EdgeInsets.all(30) : const EdgeInsets.all(15), child: DynamicTable( withSelectAll: true, cellDecoration: containerDecoration, @@ -160,21 +153,16 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout { device.unit?.name ?? '', device.room?.name ?? '', device.batteryLevel != null ? '${device.batteryLevel}%' : '-', - formatDateTime( - DateTime.fromMillisecondsSinceEpoch((device.createTime ?? 0) * 1000)), + formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.createTime ?? 0) * 1000)), device.online == true ? 'Online' : 'Offline', - formatDateTime( - DateTime.fromMillisecondsSinceEpoch((device.updateTime ?? 0) * 1000)), + formatDateTime(DateTime.fromMillisecondsSinceEpoch((device.updateTime ?? 0) * 1000)), ]; }).toList(), onSelectionChanged: (selectedRows) { context.read().add(UpdateSelection(selectedRows)); }, - initialSelectedIds: context - .read() - .selectedDevices - .map((device) => device.uuid!) - .toList(), + initialSelectedIds: + context.read().selectedDevices.map((device) => device.uuid!).toList(), isEmpty: devicesToShow.isEmpty, ), ), diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart b/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart index a2382ba6..22ac90d7 100644 --- a/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart +++ b/lib/pages/device_managment/ceiling_sensor/bloc/bloc.dart @@ -1,4 +1,5 @@ import 'dart:async'; + import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/event.dart'; @@ -23,12 +24,10 @@ class CeilingSensorBloc extends Bloc { on(_onFactoryReset); } - void _fetchCeilingSensorStatus( - CeilingInitialEvent event, Emitter emit) async { + void _fetchCeilingSensorStatus(CeilingInitialEvent event, Emitter emit) async { emit(CeilingLoadingInitialState()); try { - var response = - await DevicesManagementApi().getDeviceStatus(event.deviceId); + var response = await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = CeilingSensorModel.fromJson(response.status); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); // _listenToChanges(); @@ -57,8 +56,7 @@ class CeilingSensorBloc extends Bloc { // } catch (_) {} // } - void _changeValue( - CeilingChangeValueEvent event, Emitter emit) async { + void _changeValue(CeilingChangeValueEvent event, Emitter emit) async { emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus)); if (event.code == 'sensitivity') { deviceStatus.sensitivity = event.value; @@ -79,8 +77,7 @@ class CeilingSensorBloc extends Bloc { ); } - Future _onBatchControl( - CeilingBatchControlEvent event, Emitter emit) async { + Future _onBatchControl(CeilingBatchControlEvent event, Emitter emit) async { emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus)); if (event.code == 'sensitivity') { deviceStatus.sensitivity = event.value; @@ -123,11 +120,9 @@ class CeilingSensorBloc extends Bloc { try { late bool response; if (isBatch) { - response = await DevicesManagementApi() - .deviceBatchControl(deviceId, code, value); + response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); } else { - response = await DevicesManagementApi() - .deviceControl(deviceId, Status(code: code, value: value)); + response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); } if (!response) { @@ -145,16 +140,17 @@ class CeilingSensorBloc extends Bloc { }); } - FutureOr _getDeviceReports(GetCeilingDeviceReportsEvent event, - Emitter emit) async { + FutureOr _getDeviceReports(GetCeilingDeviceReportsEvent event, Emitter emit) async { if (event.code.isEmpty) { emit(ShowCeilingDescriptionState(description: reportString)); return; } else { emit(CeilingReportsLoadingState()); + final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch; + final to = DateTime.now().millisecondsSinceEpoch; try { - await DevicesManagementApi.getDeviceReports(deviceId, event.code) + await DevicesManagementApi.getDeviceReportsByDate(deviceId, event.code, from.toString(), to.toString()) .then((value) { emit(CeilingReportsState(deviceReport: value)); }); @@ -165,23 +161,19 @@ class CeilingSensorBloc extends Bloc { } } - void _showDescription( - ShowCeilingDescriptionEvent event, Emitter emit) { + void _showDescription(ShowCeilingDescriptionEvent event, Emitter emit) { emit(ShowCeilingDescriptionState(description: event.description)); } - void _backToGridView( - BackToCeilingGridViewEvent event, Emitter emit) { + void _backToGridView(BackToCeilingGridViewEvent event, Emitter emit) { emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); } FutureOr _fetchCeilingSensorBatchControl( - CeilingFetchDeviceStatusEvent event, - Emitter emit) async { + CeilingFetchDeviceStatusEvent event, Emitter emit) async { emit(CeilingLoadingInitialState()); try { - var response = - await DevicesManagementApi().getBatchStatus(event.devicesIds); + var response = await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = CeilingSensorModel.fromJson(response.status); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); } catch (e) { @@ -190,8 +182,7 @@ class CeilingSensorBloc extends Bloc { } } - FutureOr _onFactoryReset( - CeilingFactoryResetEvent event, Emitter emit) async { + FutureOr _onFactoryReset(CeilingFactoryResetEvent event, Emitter emit) async { emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus)); try { final response = await DevicesManagementApi().factoryReset( diff --git a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart index 48ebbcad..07cd9c7a 100644 --- a/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart +++ b/lib/pages/device_managment/garage_door/widgets/schedule__garage_table.dart @@ -26,8 +26,7 @@ class ScheduleGarageTableWidget extends StatelessWidget { Table( border: TableBorder.all( color: ColorsManager.graysColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(20), topRight: Radius.circular(20)), + borderRadius: const BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)), ), children: [ TableRow( @@ -51,21 +50,17 @@ class ScheduleGarageTableWidget extends StatelessWidget { BlocBuilder( builder: (context, state) { if (state is ScheduleGarageLoadingState) { - return const SizedBox( - height: 200, - child: Center(child: CircularProgressIndicator())); + return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator())); } - if (state is GarageDoorLoadedState && - state.status.schedules == null) { + if (state is GarageDoorLoadedState && state.status.schedules?.isEmpty == true) { return _buildEmptyState(context); } else if (state is GarageDoorLoadedState) { return Container( height: 200, decoration: BoxDecoration( border: Border.all(color: ColorsManager.graysColor), - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(20), - bottomRight: Radius.circular(20)), + borderRadius: + const BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), ), child: _buildTableBody(state, context)); } @@ -83,8 +78,7 @@ class ScheduleGarageTableWidget extends StatelessWidget { height: 200, decoration: BoxDecoration( border: Border.all(color: ColorsManager.graysColor), - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), + borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)), ), child: Center( child: Column( @@ -118,8 +112,7 @@ class ScheduleGarageTableWidget extends StatelessWidget { children: [ if (state.status.schedules != null) for (int i = 0; i < state.status.schedules!.length; i++) - _buildScheduleRow( - state.status.schedules![i], i, context, state), + _buildScheduleRow(state.status.schedules![i], i, context, state), ], ), ), @@ -141,8 +134,7 @@ class ScheduleGarageTableWidget extends StatelessWidget { ); } - TableRow _buildScheduleRow(ScheduleModel schedule, int index, - BuildContext context, GarageDoorLoadedState state) { + TableRow _buildScheduleRow(ScheduleModel schedule, int index, BuildContext context, GarageDoorLoadedState state) { return TableRow( children: [ Center( @@ -160,8 +152,7 @@ class ScheduleGarageTableWidget extends StatelessWidget { width: 24, height: 24, child: schedule.enable - ? const Icon(Icons.radio_button_checked, - color: ColorsManager.blueColor) + ? const Icon(Icons.radio_button_checked, color: ColorsManager.blueColor) : const Icon( Icons.radio_button_unchecked, color: ColorsManager.grayColor, @@ -169,9 +160,7 @@ class ScheduleGarageTableWidget extends StatelessWidget { ), ), ), - Center( - child: Text(_getSelectedDays( - ScheduleModel.parseSelectedDays(schedule.days)))), + Center(child: Text(_getSelectedDays(ScheduleModel.parseSelectedDays(schedule.days)))), Center(child: Text(formatIsoStringToTime(schedule.time, context))), Center(child: Text(schedule.function.value ? 'On' : 'Off')), Center( @@ -181,24 +170,18 @@ class ScheduleGarageTableWidget extends StatelessWidget { TextButton( style: TextButton.styleFrom(padding: EdgeInsets.zero), onPressed: () { - GarageDoorDialogHelper.showAddGarageDoorScheduleDialog( - context, - schedule: schedule, - index: index, - isEdit: true); + GarageDoorDialogHelper.showAddGarageDoorScheduleDialog(context, + schedule: schedule, index: index, isEdit: true); }, child: Text( 'Edit', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.blueColor), + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blueColor), ), ), TextButton( style: TextButton.styleFrom(padding: EdgeInsets.zero), onPressed: () { - context - .read() - .add(DeleteGarageDoorScheduleEvent( + context.read().add(DeleteGarageDoorScheduleEvent( index: index, scheduleId: schedule.scheduleId, deviceId: state.status.uuid, @@ -206,8 +189,7 @@ class ScheduleGarageTableWidget extends StatelessWidget { }, child: Text( 'Delete', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.blueColor), + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.blueColor), ), ), ], diff --git a/lib/pages/device_managment/gateway/bloc/gate_way_bloc.dart b/lib/pages/device_managment/gateway/bloc/gate_way_bloc.dart index f4c16ba0..e14672ae 100644 --- a/lib/pages/device_managment/gateway/bloc/gate_way_bloc.dart +++ b/lib/pages/device_managment/gateway/bloc/gate_way_bloc.dart @@ -16,12 +16,10 @@ class GateWayBloc extends Bloc { on(_onFactoryReset); } - FutureOr _getGatWayById( - GatWayById event, Emitter emit) async { + FutureOr _getGatWayById(GatWayById event, Emitter emit) async { emit(GatewayLoadingState()); try { - List devicesList = - await DevicesManagementApi.getDevicesByGatewayId(event.getWayId); + List devicesList = await DevicesManagementApi.getDevicesByGatewayId(event.getWayId); emit(UpdateGatewayState(list: devicesList)); } catch (e) { @@ -30,8 +28,7 @@ class GateWayBloc extends Bloc { } } - FutureOr _onFactoryReset( - GateWayFactoryReset event, Emitter emit) async { + FutureOr _onFactoryReset(GateWayFactoryReset event, Emitter emit) async { emit(GatewayLoadingState()); try { final response = await DevicesManagementApi().factoryReset( diff --git a/lib/pages/device_managment/gateway/view/gateway_view.dart b/lib/pages/device_managment/gateway/view/gateway_view.dart index ce9b06da..2bfc6822 100644 --- a/lib/pages/device_managment/gateway/view/gateway_view.dart +++ b/lib/pages/device_managment/gateway/view/gateway_view.dart @@ -74,7 +74,7 @@ class _DeviceItem extends StatelessWidget { padding: const EdgeInsets.all(8), color: ColorsManager.whiteColors, child: SvgPicture.asset( - device.icon, + device.icon ?? 'assets/icons/gateway.svg', width: 35, height: 35, fit: BoxFit.contain, diff --git a/lib/pages/device_managment/shared/device_controls_container.dart b/lib/pages/device_managment/shared/device_controls_container.dart index 158b43cf..4f1dea59 100644 --- a/lib/pages/device_managment/shared/device_controls_container.dart +++ b/lib/pages/device_managment/shared/device_controls_container.dart @@ -2,15 +2,17 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class DeviceControlsContainer extends StatelessWidget { - const DeviceControlsContainer({required this.child, super.key}); + const DeviceControlsContainer({required this.child, this.padding, super.key}); final Widget child; + final double? padding; @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), - color: ColorsManager.grayColor.withOpacity(0.2), + color: ColorsManager.greyColor.withOpacity(0.2), + // boxShadow: [ // BoxShadow( // color: ColorsManager.blackColor.withOpacity(0.05), @@ -19,7 +21,7 @@ class DeviceControlsContainer extends StatelessWidget { // spreadRadius: 0) // ], ), - padding: const EdgeInsets.all(12), + padding: EdgeInsets.all(padding ?? 12), child: child, ); } diff --git a/lib/pages/device_managment/shared/toggle_widget.dart b/lib/pages/device_managment/shared/toggle_widget.dart index 942c1b32..ad0ba8ad 100644 --- a/lib/pages/device_managment/shared/toggle_widget.dart +++ b/lib/pages/device_managment/shared/toggle_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.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'; @@ -30,13 +31,7 @@ class ToggleWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - color: ColorsManager.greyColor.withOpacity(0.2), - border: Border.all(color: ColorsManager.boxDivider), - ), - padding: const EdgeInsets.all(16), + return DeviceControlsContainer( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/pages/visitor_password/model/device_model.dart b/lib/pages/visitor_password/model/device_model.dart index 6d1c714d..c357da52 100644 --- a/lib/pages/visitor_password/model/device_model.dart +++ b/lib/pages/visitor_password/model/device_model.dart @@ -1,6 +1,6 @@ +import 'package:syncrow_web/utils/constants/app_enum.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/enum/device_types.dart'; -import 'package:syncrow_web/utils/constants/app_enum.dart'; class DeviceModel { dynamic productUuid; @@ -51,7 +51,6 @@ class DeviceModel { factory DeviceModel.fromJson(Map json) { String tempIcon = ''; DeviceType type = devicesTypesMap[json['productType']] ?? DeviceType.Other; - if (type == DeviceType.LightBulb) { tempIcon = Assets.lightBulb; } else if (type == DeviceType.CeilingSensor || type == DeviceType.WallSensor) { @@ -66,9 +65,16 @@ class DeviceModel { tempIcon = Assets.gangSwitch; } else if (type == DeviceType.Gateway) { tempIcon = Assets.gateway; + } else if (type == DeviceType.OneGang) { + tempIcon = Assets.oneGang; + } else if (type == DeviceType.TwoGang) { + tempIcon = Assets.twoGang; + } else if (type == DeviceType.WH) { + tempIcon = Assets.waterHeater; } else { - tempIcon = Assets.logo; + tempIcon = Assets.blackLogo; } + return DeviceModel( productUuid: json['productUuid'], productType: json['productType'], diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index b22eedbb..dc0af9a8 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -68,8 +68,7 @@ class DevicesManagementApi { } } - Future deviceBatchControl( - List uuids, String code, dynamic value) async { + Future deviceBatchControl(List uuids, String code, dynamic value) async { try { final body = { 'devicesUuid': uuids, @@ -93,8 +92,7 @@ class DevicesManagementApi { } } - static Future> getDevicesByGatewayId( - String gatewayId) async { + static Future> getDevicesByGatewayId(String gatewayId) async { final response = await HTTPService().get( path: ApiEndpoints.gatewayApi.replaceAll('{gatewayUuid}', gatewayId), showServerMessage: false, @@ -128,9 +126,7 @@ class DevicesManagementApi { String code, ) async { final response = await HTTPService().get( - path: ApiEndpoints.getDeviceLogs - .replaceAll('{uuid}', uuid) - .replaceAll('{code}', code), + path: ApiEndpoints.getDeviceLogs.replaceAll('{uuid}', uuid).replaceAll('{code}', code), showServerMessage: false, expectedResponseModel: (json) { return DeviceReport.fromJson(json); @@ -139,8 +135,7 @@ class DevicesManagementApi { return response; } - static Future getDeviceReportsByDate(String uuid, String code, - [String? from, String? to]) async { + static Future getDeviceReportsByDate(String uuid, String code, [String? from, String? to]) async { final response = await HTTPService().get( path: ApiEndpoints.getDeviceLogsByDate .replaceAll('{uuid}', uuid) @@ -179,8 +174,7 @@ class DevicesManagementApi { } } - Future addScheduleRecord( - ScheduleEntry sendSchedule, String uuid) async { + Future addScheduleRecord(ScheduleEntry sendSchedule, String uuid) async { try { final response = await HTTPService().post( path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), @@ -197,13 +191,10 @@ class DevicesManagementApi { } } - Future> getDeviceSchedules( - String uuid, String category) async { + Future> getDeviceSchedules(String uuid, String category) async { try { final response = await HTTPService().get( - path: ApiEndpoints.getScheduleByDeviceId - .replaceAll('{deviceUuid}', uuid) - .replaceAll('{category}', category), + path: ApiEndpoints.getScheduleByDeviceId.replaceAll('{deviceUuid}', uuid).replaceAll('{category}', category), showServerMessage: true, expectedResponseModel: (json) { List schedules = []; @@ -220,10 +211,7 @@ class DevicesManagementApi { } } - Future updateScheduleRecord( - {required bool enable, - required String uuid, - required String scheduleId}) async { + Future updateScheduleRecord({required bool enable, required String uuid, required String scheduleId}) async { try { final response = await HTTPService().put( path: ApiEndpoints.updateScheduleByDeviceId diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 05ef096e..4ac64bb6 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -13,12 +13,10 @@ class Assets { static const String rightLine = "assets/images/right_line.png"; static const String google = "assets/images/google.svg"; static const String facebook = "assets/images/facebook.svg"; - static const String invisiblePassword = - "assets/images/Password_invisible.svg"; + static const String invisiblePassword = "assets/images/Password_invisible.svg"; static const String visiblePassword = "assets/images/password_visible.svg"; static const String accessIcon = "assets/images/access_icon.svg"; - static const String spaseManagementIcon = - "assets/images/spase_management_icon.svg"; + static const String spaseManagementIcon = "assets/images/spase_management_icon.svg"; static const String devicesIcon = "assets/images/devices_icon.svg"; static const String moveinIcon = "assets/images/movein_icon.svg"; static const String constructionIcon = "assets/images/construction_icon.svg"; @@ -31,15 +29,13 @@ class Assets { static const String emptyTable = "assets/images/empty_table.svg"; // General assets - static const String motionlessDetection = - "assets/icons/motionless_detection.svg"; + static const String motionlessDetection = "assets/icons/motionless_detection.svg"; static const String acHeating = "assets/icons/ac_heating.svg"; static const String acPowerOff = "assets/icons/ac_power_off.svg"; static const String acFanMiddle = "assets/icons/ac_fan_middle.svg"; static const String switchAlarmSound = "assets/icons/switch_alarm_sound.svg"; static const String resetOff = "assets/icons/reset_off.svg"; - static const String sensitivityOperationIcon = - "assets/icons/sesitivity_operation_icon.svg"; + static const String sensitivityOperationIcon = "assets/icons/sesitivity_operation_icon.svg"; static const String motionDetection = "assets/icons/motion_detection.svg"; static const String freezing = "assets/icons/freezing.svg"; static const String indicator = "assets/icons/indicator.svg"; @@ -60,56 +56,35 @@ class Assets { static const String celsiusDegrees = "assets/icons/celsius_degrees.svg"; static const String masterState = "assets/icons/master_state.svg"; static const String acPower = "assets/icons/ac_power.svg"; - static const String farDetectionFunction = - "assets/icons/far_detection_function.svg"; + static const String farDetectionFunction = "assets/icons/far_detection_function.svg"; static const String nobodyTime = "assets/icons/nobody_time.svg"; // Automation functions - static const String tempPasswordUnlock = - "assets/icons/automation_functions/temp_password_unlock.svg"; - static const String doorlockNormalOpen = - "assets/icons/automation_functions/doorlock_normal_open.svg"; - static const String doorbell = - "assets/icons/automation_functions/doorbell.svg"; - static const String remoteUnlockViaApp = - "assets/icons/automation_functions/remote_unlock_via_app.svg"; - static const String doubleLock = - "assets/icons/automation_functions/double_lock.svg"; - static const String selfTestResult = - "assets/icons/automation_functions/self_test_result.svg"; - static const String lockAlarm = - "assets/icons/automation_functions/lock_alarm.svg"; - static const String presenceState = - "assets/icons/automation_functions/presence_state.svg"; - static const String currentTemp = - "assets/icons/automation_functions/current_temp.svg"; - static const String presence = - "assets/icons/automation_functions/presence.svg"; - static const String residualElectricity = - "assets/icons/automation_functions/residual_electricity.svg"; - static const String hijackAlarm = - "assets/icons/automation_functions/hijack_alarm.svg"; - static const String passwordUnlock = - "assets/icons/automation_functions/password_unlock.svg"; - static const String remoteUnlockRequest = - "assets/icons/automation_functions/remote_unlock_req.svg"; - static const String cardUnlock = - "assets/icons/automation_functions/card_unlock.svg"; + static const String tempPasswordUnlock = "assets/icons/automation_functions/temp_password_unlock.svg"; + static const String doorlockNormalOpen = "assets/icons/automation_functions/doorlock_normal_open.svg"; + static const String doorbell = "assets/icons/automation_functions/doorbell.svg"; + static const String remoteUnlockViaApp = "assets/icons/automation_functions/remote_unlock_via_app.svg"; + static const String doubleLock = "assets/icons/automation_functions/double_lock.svg"; + static const String selfTestResult = "assets/icons/automation_functions/self_test_result.svg"; + static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg"; + static const String presenceState = "assets/icons/automation_functions/presence_state.svg"; + static const String currentTemp = "assets/icons/automation_functions/current_temp.svg"; + static const String presence = "assets/icons/automation_functions/presence.svg"; + static const String residualElectricity = "assets/icons/automation_functions/residual_electricity.svg"; + static const String hijackAlarm = "assets/icons/automation_functions/hijack_alarm.svg"; + static const String passwordUnlock = "assets/icons/automation_functions/password_unlock.svg"; + static const String remoteUnlockRequest = "assets/icons/automation_functions/remote_unlock_req.svg"; + static const String cardUnlock = "assets/icons/automation_functions/card_unlock.svg"; static const String motion = "assets/icons/automation_functions/motion.svg"; - static const String fingerprintUnlock = - "assets/icons/automation_functions/fingerprint_unlock.svg"; + static const String fingerprintUnlock = "assets/icons/automation_functions/fingerprint_unlock.svg"; // Presence Sensor Assets static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg"; - static const String sensorPresenceIcon = - "assets/icons/sensor_presence_ic.svg"; + static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg"; static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg"; - static const String illuminanceRecordIcon = - "assets/icons/illuminance_record_ic.svg"; - static const String presenceRecordIcon = - "assets/icons/presence_record_ic.svg"; - static const String helpDescriptionIcon = - "assets/icons/help_description_ic.svg"; + static const String illuminanceRecordIcon = "assets/icons/illuminance_record_ic.svg"; + static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg"; + static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg"; static const String lightPulp = "assets/icons/light_pulb.svg"; static const String acDevice = "assets/icons/ac_device.svg"; @@ -183,9 +158,14 @@ class Assets { //assets/icons/water_leak_normal.svg static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg'; //assets/icons/water_leak_detected.svg - static const String waterLeakDetected = - 'assets/icons/water_leak_detected.svg'; + static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg'; //assets/icons/automation_records.svg static const String automationRecords = 'assets/icons/automation_records.svg'; + + //assets/icons/1gang.svg + static const String oneGang = 'assets/icons/1gang.svg'; + + //assets/icons/2gang.svg + static const String twoGang = 'assets/icons/2gang.svg'; } diff --git a/lib/utils/enum/device_types.dart b/lib/utils/enum/device_types.dart index 94821ef6..9ddead07 100644 --- a/lib/utils/enum/device_types.dart +++ b/lib/utils/enum/device_types.dart @@ -4,10 +4,13 @@ enum DeviceType { DoorLock, Curtain, Blind, + OneGang, + TwoGang, ThreeGang, Gateway, CeilingSensor, WallSensor, + WH, Other, } @@ -18,4 +21,8 @@ Map devicesTypesMap = { "DL": DeviceType.DoorLock, "WPS": DeviceType.WallSensor, "3G": DeviceType.ThreeGang, + "2G": DeviceType.TwoGang, + "1G": DeviceType.OneGang, + "CUR": DeviceType.Curtain, + "WH": DeviceType.WH, }; From 79ec89024401a9c869d7cdceccb37f061999ebd9 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Tue, 8 Oct 2024 20:15:52 +0300 Subject: [PATCH 64/65] fix oct/8 issues --- .../bloc/device_managment_bloc.dart | 13 ++ .../garage_door/bloc/garage_door_bloc.dart | 182 +++++++++--------- .../garage_door/bloc/garage_door_event.dart | 22 ++- .../helper/garage_door_helper.dart | 138 ++++++------- .../garage_door/models/garage_door_model.dart | 2 +- .../widgets/notification_dialog.dart | 29 ++- .../bloc/one_gang_glass_switch_bloc.dart | 50 ++--- .../one_gang_glass_switch_control_view.dart | 2 +- .../shared/toggle_widget.dart | 1 + ..._gang_glass_switch_batch_control_view.dart | 6 +- .../three_gang_glass_switch_control_view.dart | 6 +- .../water_heater/bloc/water_heater_bloc.dart | 93 +++++---- .../water_heater/bloc/water_heater_event.dart | 16 ++ .../helper/add_schedule_dialog_helper.dart | 97 ++++------ .../water_heater/models/schedule_entry.dart | 12 +- .../widgets/water_leak_notifi_dialog.dart | 22 ++- .../visitor_password/model/device_model.dart | 6 + lib/services/devices_mang_api.dart | 16 ++ lib/utils/enum/device_types.dart | 28 +++ 19 files changed, 406 insertions(+), 335 deletions(-) diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart index 5c35f9e9..46eedaaf 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart @@ -15,6 +15,9 @@ class DeviceManagementBloc extends Bloc _selectedDevices = []; List _filteredDevices = []; String productName = ''; + String? currentCommunity; + String? currentUnitName; + String? currentProductName; DeviceManagementBloc() : super(DeviceManagementInitial()) { on(_onFetchDevices); @@ -234,6 +237,16 @@ class DeviceManagementBloc extends Bloc emit) { + if (event.productName == currentProductName && + event.community == currentCommunity && + event.unitName == currentUnitName) { + return; + } + + currentProductName = event.productName; + currentCommunity = event.community; + currentUnitName = event.unitName; + if ((event.community == null || event.community!.isEmpty) && (event.unitName == null || event.unitName!.isEmpty) && (event.productName == null || event.productName!.isEmpty)) { diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart index 69e58c65..7060c668 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart @@ -38,14 +38,13 @@ class GarageDoorBloc extends Bloc { on(_onBatchControl); on(_onFetchBatchStatus); on(_onFactoryReset); + on(_onEditSchedule); } - void _fetchGarageDoorStatus( - GarageDoorInitialEvent event, Emitter emit) async { + void _fetchGarageDoorStatus(GarageDoorInitialEvent event, Emitter emit) async { emit(GarageDoorLoadingState()); try { - var response = - await DevicesManagementApi().getDeviceStatus(event.deviceId); + var response = await DevicesManagementApi().getDeviceStatus(event.deviceId); deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status); emit(GarageDoorLoadedState(status: deviceStatus)); } catch (e) { @@ -53,22 +52,18 @@ class GarageDoorBloc extends Bloc { } } - Future _onFetchBatchStatus(GarageDoorFetchBatchStatusEvent event, - Emitter emit) async { + Future _onFetchBatchStatus(GarageDoorFetchBatchStatusEvent event, Emitter emit) async { emit(GarageDoorLoadingState()); try { - final status = - await DevicesManagementApi().getBatchStatus(event.deviceIds); - deviceStatus = - GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status); + final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status); emit(GarageDoorBatchStatusLoaded(deviceStatus)); } catch (e) { emit(GarageDoorBatchControlError(e.toString())); } } - Future _addSchedule( - AddGarageDoorScheduleEvent event, Emitter emit) async { + Future _addSchedule(AddGarageDoorScheduleEvent event, Emitter emit) async { try { ScheduleEntry newSchedule = ScheduleEntry( category: event.category, @@ -76,11 +71,9 @@ class GarageDoorBloc extends Bloc { function: Status(code: 'switch_1', value: event.functionOn), days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), ); - bool success = - await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); + bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId); if (success) { - add(FetchGarageDoorSchedulesEvent( - deviceId: deviceId, category: 'switch_1')); + add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1')); } else { emit(GarageDoorLoadedState(status: deviceStatus)); } @@ -89,19 +82,16 @@ class GarageDoorBloc extends Bloc { } } - void _onUpdateCountdownAlarm( - UpdateCountdownAlarmEvent event, Emitter emit) { + void _onUpdateCountdownAlarm(UpdateCountdownAlarmEvent event, Emitter emit) { final currentState = state; if (currentState is GarageDoorLoadedState) { emit(currentState.copyWith( - status: - currentState.status.copyWith(countdownAlarm: event.countdownAlarm), + status: currentState.status.copyWith(countdownAlarm: event.countdownAlarm), )); } } - void _onUpdateTrTimeCon( - UpdateTrTimeConEvent event, Emitter emit) { + void _onUpdateTrTimeCon(UpdateTrTimeConEvent event, Emitter emit) { final currentState = state; if (currentState is GarageDoorLoadedState) { emit(currentState.copyWith( @@ -110,8 +100,7 @@ class GarageDoorBloc extends Bloc { } } - Future _updateSchedule(UpdateGarageDoorScheduleEvent event, - Emitter emit) async { + Future _updateSchedule(UpdateGarageDoorScheduleEvent event, Emitter emit) async { try { final updatedSchedules = deviceStatus.schedules?.map((schedule) { if (schedule.scheduleId == event.scheduleId) { @@ -138,15 +127,12 @@ class GarageDoorBloc extends Bloc { } } - Future _deleteSchedule(DeleteGarageDoorScheduleEvent event, - Emitter emit) async { + Future _deleteSchedule(DeleteGarageDoorScheduleEvent event, Emitter emit) async { try { - bool success = await DevicesManagementApi() - .deleteScheduleRecord(deviceStatus.uuid, event.scheduleId); + bool success = await DevicesManagementApi().deleteScheduleRecord(deviceStatus.uuid, event.scheduleId); if (success) { - final updatedSchedules = deviceStatus.schedules - ?.where((schedule) => schedule.scheduleId != event.scheduleId) - .toList(); + final updatedSchedules = + deviceStatus.schedules?.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules); emit(GarageDoorLoadedState(status: deviceStatus)); } else { @@ -157,12 +143,11 @@ class GarageDoorBloc extends Bloc { } } - Future _fetchSchedules(FetchGarageDoorSchedulesEvent event, - Emitter emit) async { + Future _fetchSchedules(FetchGarageDoorSchedulesEvent event, Emitter emit) async { emit(ScheduleGarageLoadingState()); try { - List schedules = await DevicesManagementApi() - .getDeviceSchedules(deviceStatus.uuid, event.category); + List schedules = + await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category); deviceStatus = deviceStatus.copyWith(schedules: schedules); emit( GarageDoorLoadedState( @@ -180,37 +165,30 @@ class GarageDoorBloc extends Bloc { } } - Future _updateSelectedTime( - UpdateSelectedTimeEvent event, Emitter emit) async { + Future _updateSelectedTime(UpdateSelectedTimeEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { emit(currentState.copyWith(selectedTime: event.selectedTime)); } } - Future _updateSelectedDay( - UpdateSelectedDayEvent event, Emitter emit) async { + Future _updateSelectedDay(UpdateSelectedDayEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { List updatedDays = List.from(currentState.selectedDays); updatedDays[event.dayIndex] = event.isSelected; - emit(currentState.copyWith( - selectedDays: updatedDays, selectedTime: currentState.selectedTime)); + emit(currentState.copyWith(selectedDays: updatedDays, selectedTime: currentState.selectedTime)); } } - Future _updateFunctionOn( - UpdateFunctionOnEvent event, Emitter emit) async { + Future _updateFunctionOn(UpdateFunctionOnEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { - emit(currentState.copyWith( - functionOn: event.functionOn, - selectedTime: currentState.selectedTime)); + emit(currentState.copyWith(functionOn: event.functionOn, selectedTime: currentState.selectedTime)); } } - Future _initializeAddSchedule( - InitializeAddScheduleEvent event, Emitter emit) async { + Future _initializeAddSchedule(InitializeAddScheduleEvent event, Emitter emit) async { final currentState = state; if (currentState is GarageDoorLoadedState) { emit(currentState.copyWith( @@ -222,25 +200,20 @@ class GarageDoorBloc extends Bloc { } } - Future _fetchRecords( - FetchGarageDoorRecordsEvent event, Emitter emit) async { + Future _fetchRecords(FetchGarageDoorRecordsEvent event, Emitter emit) async { emit(GarageDoorReportsLoadingState()); try { - final from = DateTime.now() - .subtract(const Duration(days: 30)) - .millisecondsSinceEpoch; + final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch; final to = DateTime.now().millisecondsSinceEpoch; final DeviceReport records = - await DevicesManagementApi.getDeviceReportsByDate( - event.deviceId, 'switch_1', from.toString(), to.toString()); + await DevicesManagementApi.getDeviceReportsByDate(event.deviceId, 'switch_1', from.toString(), to.toString()); emit(GarageDoorReportsState(deviceReport: records)); } catch (e) { emit(GarageDoorReportsFailedState(error: e.toString())); } } - Future _onBatchControl( - GarageDoorBatchControlEvent event, Emitter emit) async { + Future _onBatchControl(GarageDoorBatchControlEvent event, Emitter emit) async { final oldValue = event.code == 'switch_1' ? deviceStatus.switch1 : false; _updateLocalValue(event.code, event.value); @@ -260,13 +233,11 @@ class GarageDoorBloc extends Bloc { } } - void _backToGridView( - BackToGarageDoorGridViewEvent event, Emitter emit) { + void _backToGridView(BackToGarageDoorGridViewEvent event, Emitter emit) { emit(GarageDoorLoadedState(status: deviceStatus)); } - void _handleUpdate( - GarageDoorUpdatedEvent event, Emitter emit) { + void _handleUpdate(GarageDoorUpdatedEvent event, Emitter emit) { emit(GarageDoorLoadedState(status: deviceStatus)); } @@ -282,11 +253,9 @@ class GarageDoorBloc extends Bloc { late bool status; await Future.delayed(const Duration(milliseconds: 500)); if (isBatch) { - status = await DevicesManagementApi() - .deviceBatchControl(deviceId, code, value); + status = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); } else { - status = await DevicesManagementApi() - .deviceControl(deviceId, Status(code: code, value: value)); + status = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); } if (!status) { @@ -301,12 +270,10 @@ class GarageDoorBloc extends Bloc { } } - Future _onFactoryReset( - GarageDoorFactoryResetEvent event, Emitter emit) async { + Future _onFactoryReset(GarageDoorFactoryResetEvent event, Emitter emit) async { emit(GarageDoorLoadingState()); try { - final response = await DevicesManagementApi() - .factoryReset(event.factoryReset, event.deviceId); + final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId); if (!response) { emit(const GarageDoorErrorState(message: 'Failed to reset device')); } else { @@ -317,47 +284,34 @@ class GarageDoorBloc extends Bloc { } } - void _increaseDelay( - IncreaseGarageDoorDelayEvent event, Emitter emit) async { + void _increaseDelay(IncreaseGarageDoorDelayEvent event, Emitter emit) async { // if (deviceStatus.countdown1 != 0) { try { - deviceStatus = deviceStatus.copyWith( - delay: deviceStatus.delay + Duration(minutes: 10)); + deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay + Duration(minutes: 10)); emit(GarageDoorLoadedState(status: deviceStatus)); - add(GarageDoorControlEvent( - deviceId: event.deviceId, - value: deviceStatus.delay.inSeconds, - code: 'countdown_1')); + add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1')); } catch (e) { emit(GarageDoorErrorState(message: e.toString())); } // } } - void _decreaseDelay( - DecreaseGarageDoorDelayEvent event, Emitter emit) async { + void _decreaseDelay(DecreaseGarageDoorDelayEvent event, Emitter emit) async { // if (deviceStatus.countdown1 != 0) { try { if (deviceStatus.delay.inMinutes > 10) { - deviceStatus = deviceStatus.copyWith( - delay: deviceStatus.delay - Duration(minutes: 10)); + deviceStatus = deviceStatus.copyWith(delay: deviceStatus.delay - Duration(minutes: 10)); } emit(GarageDoorLoadedState(status: deviceStatus)); - add(GarageDoorControlEvent( - deviceId: event.deviceId, - value: deviceStatus.delay.inSeconds, - code: 'countdown_1')); + add(GarageDoorControlEvent(deviceId: event.deviceId, value: deviceStatus.delay.inSeconds, code: 'countdown_1')); } catch (e) { emit(GarageDoorErrorState(message: e.toString())); } //} } - void _garageDoorControlEvent( - GarageDoorControlEvent event, Emitter emit) async { - final oldValue = event.code == 'countdown_1' - ? deviceStatus.countdown1 - : deviceStatus.switch1; + void _garageDoorControlEvent(GarageDoorControlEvent event, Emitter emit) async { + final oldValue = event.code == 'countdown_1' ? deviceStatus.countdown1 : deviceStatus.switch1; _updateLocalValue(event.code, event.value); emit(GarageDoorLoadedState(status: deviceStatus)); final success = await _runDeBouncer( @@ -373,8 +327,7 @@ class GarageDoorBloc extends Bloc { } } - void _revertValue( - String code, dynamic oldValue, Emitter emit) { + void _revertValue(String code, dynamic oldValue, Emitter emit) { switch (code) { case 'switch_1': if (oldValue is bool) { @@ -383,8 +336,7 @@ class GarageDoorBloc extends Bloc { break; case 'countdown_1': if (oldValue is int) { - deviceStatus = deviceStatus.copyWith( - countdown1: oldValue, delay: Duration(seconds: oldValue)); + deviceStatus = deviceStatus.copyWith(countdown1: oldValue, delay: Duration(seconds: oldValue)); } break; // Add other cases if needed @@ -406,8 +358,7 @@ class GarageDoorBloc extends Bloc { break; case 'countdown_1': if (value is int) { - deviceStatus = deviceStatus.copyWith( - countdown1: value, delay: Duration(seconds: value)); + deviceStatus = deviceStatus.copyWith(countdown1: value, delay: Duration(seconds: value)); } break; case 'countdown_alarm': @@ -420,6 +371,25 @@ class GarageDoorBloc extends Bloc { deviceStatus = deviceStatus.copyWith(trTimeCon: value); } break; + case 'door_state_1': + if (value is String) { + deviceStatus = deviceStatus.copyWith(doorState1: value); + } + break; + case 'door_control_1': + if (value is String) { + deviceStatus = deviceStatus.copyWith(doorControl1: value); + } + break; + case 'voice_control_1': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(voiceControl1: value); + } + break; + case 'door_contact_state': + if (value is bool) { + deviceStatus = deviceStatus.copyWith(doorContactState: value); + } default: break; } @@ -430,4 +400,24 @@ class GarageDoorBloc extends Bloc { _timer?.cancel(); return super.close(); } + + FutureOr _onEditSchedule(EditGarageDoorScheduleEvent event, Emitter emit) async { + try { + ScheduleEntry newSchedule = ScheduleEntry( + scheduleId: event.scheduleId, + category: event.category, + time: formatTimeOfDayToISO(event.time), + function: Status(code: 'switch_1', value: event.functionOn), + days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), + ); + bool success = await DevicesManagementApi().editScheduleRecord(deviceId, newSchedule); + if (success) { + add(FetchGarageDoorSchedulesEvent(deviceId: deviceId, category: 'switch_1')); + } else { + emit(GarageDoorLoadedState(status: deviceStatus)); + } + } catch (e) { + emit(GarageDoorLoadedState(status: deviceStatus)); + } + } } diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart index 24eb921e..d1fb15bb 100644 --- a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart +++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart @@ -25,8 +25,7 @@ class GarageDoorControlEvent extends GarageDoorEvent { final dynamic value; final String code; - const GarageDoorControlEvent( - {required this.deviceId, required this.value, required this.code}); + const GarageDoorControlEvent({required this.deviceId, required this.value, required this.code}); @override List get props => [deviceId, value]; @@ -46,6 +45,22 @@ class AddGarageDoorScheduleEvent extends GarageDoorEvent { }); } +class EditGarageDoorScheduleEvent extends GarageDoorEvent { + final String scheduleId; + final String category; + final TimeOfDay time; + final bool functionOn; + final List selectedDays; + + const EditGarageDoorScheduleEvent({ + required this.scheduleId, + required this.category, + required this.time, + required this.functionOn, + required this.selectedDays, + }); +} + class UpdateGarageDoorScheduleEvent extends GarageDoorEvent { final String deviceId; final String scheduleId; @@ -106,8 +121,7 @@ class FetchGarageDoorRecordsEvent extends GarageDoorEvent { final String deviceId; final String code; - const FetchGarageDoorRecordsEvent( - {required this.deviceId, required this.code}); + const FetchGarageDoorRecordsEvent({required this.deviceId, required this.code}); @override List get props => [deviceId, code]; diff --git a/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart b/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart index c30f391d..7b133d45 100644 --- a/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart +++ b/lib/pages/device_managment/garage_door/helper/garage_door_helper.dart @@ -76,35 +76,30 @@ class GarageDoorDialogHelper { padding: 8, backgroundColor: ColorsManager.boxColor, borderRadius: 15, - onPressed: isEdit == true - ? null - : () async { - TimeOfDay? time = await showTimePicker( - context: context, - initialTime: - state.selectedTime ?? TimeOfDay.now(), - builder: (context, child) { - return Theme( - data: Theme.of(context).copyWith( - colorScheme: const ColorScheme.light( - primary: ColorsManager.primaryColor, - ), - ), - child: child!, - ); - }, - ); - if (time != null) { - bloc.add(UpdateSelectedTimeEvent(time)); - } - }, + onPressed: () async { + TimeOfDay? time = await showTimePicker( + context: context, + initialTime: state.selectedTime ?? TimeOfDay.now(), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: const ColorScheme.light( + primary: ColorsManager.primaryColor, + ), + ), + child: child!, + ); + }, + ); + if (time != null) { + bloc.add(UpdateSelectedTimeEvent(time)); + } + }, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - state.selectedTime == null - ? 'Time' - : state.selectedTime!.format(context), + state.selectedTime == null ? 'Time' : state.selectedTime!.format(context), style: context.textTheme.bodySmall!.copyWith( color: ColorsManager.grayColor, ), @@ -119,8 +114,7 @@ class GarageDoorDialogHelper { ), ), const SizedBox(height: 16), - _buildDayCheckboxes(context, state.selectedDays, - isEdit: isEdit), + _buildDayCheckboxes(context, state.selectedDays, isEdit: isEdit), const SizedBox(height: 16), _buildFunctionSwitch(context, state.functionOn, isEdit), ], @@ -147,7 +141,13 @@ class GarageDoorDialogHelper { onPressed: () { if (state.selectedTime != null) { if (state.isEditing && index != null) { - return; + bloc.add(EditGarageDoorScheduleEvent( + scheduleId: schedule?.scheduleId ?? '', + category: 'switch_1', + time: state.selectedTime!, + selectedDays: state.selectedDays, + functionOn: state.functionOn, + )); } else { bloc.add(AddGarageDoorScheduleEvent( category: 'switch_1', @@ -199,9 +199,7 @@ class GarageDoorDialogHelper { return daysBoolean; } - static Widget _buildDayCheckboxes( - BuildContext context, List selectedDays, - {bool? isEdit}) { + static Widget _buildDayCheckboxes(BuildContext context, List selectedDays, {bool? isEdit}) { final dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; return Row( @@ -210,13 +208,9 @@ class GarageDoorDialogHelper { children: [ Checkbox( value: selectedDays[index], - onChanged: isEdit == true - ? null - : (bool? value) { - context - .read() - .add(UpdateSelectedDayEvent(index, value!)); - }, + onChanged: (bool? value) { + context.read().add(UpdateSelectedDayEvent(index, value!)); + }, ), Text(dayLabels[index]), ], @@ -225,27 +219,19 @@ class GarageDoorDialogHelper { ); } - static Widget _buildFunctionSwitch( - BuildContext context, bool isOn, bool? isEdit) { + static Widget _buildFunctionSwitch(BuildContext context, bool isOn, bool? isEdit) { return Row( children: [ Text( 'Function:', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.grayColor), + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.grayColor), ), const SizedBox(width: 10), Radio( value: true, groupValue: isOn, onChanged: (bool? value) { - if (isEdit == true) { - return; - } else { - context - .read() - .add(const UpdateFunctionOnEvent(functionOn: true)); - } + context.read().add(const UpdateFunctionOnEvent(functionOn: true)); }, ), const Text('On'), @@ -254,13 +240,7 @@ class GarageDoorDialogHelper { value: false, groupValue: isOn, onChanged: (bool? value) { - if (isEdit == true) { - return; - } else { - context - .read() - .add(const UpdateFunctionOnEvent(functionOn: false)); - } + context.read().add(const UpdateFunctionOnEvent(functionOn: false)); }, ), const Text('Off'), @@ -317,33 +297,36 @@ class GarageDoorDialogHelper { alertBody: TimeOutAlarmDialogBody(bloc), title: 'Time Out Alarm', onConfirm: () { - final updatedState = - context.read().state; - if (updatedState - is GarageDoorLoadedState) { + final updatedState = context.read().state; + if (updatedState is GarageDoorLoadedState) { context.read().add( GarageDoorControlEvent( - deviceId: - updatedState.status.uuid, + deviceId: updatedState.status.uuid, code: 'countdown_alarm', - value: updatedState - .status.countdownAlarm, + value: updatedState.status.countdownAlarm, ), ); Navigator.pop(context); - // context.read().add( - // GarageDoorInitialEvent( - // bloc.deviceId)); } }); }, child: ToggleWidget( icon: "-1", - value: state.status.countdownAlarm > 0, - code: 'countdown_alarm', + value: state.status.doorState1 == "close_time_alarm" ? false : true, + code: 'door_state_1', deviceId: bloc.deviceId, label: 'Alarm when door is open', - onChange: (value) {}), + onChange: (value) { + context.read().add( + GarageDoorControlEvent( + deviceId: bloc.deviceId, + code: 'door_state_1', + value: state.status.doorState1 == "close_time_alarm" + ? "unclosed_time" + : "close_time_alarm", + ), + ); + }), ), ), const SizedBox( @@ -365,23 +348,16 @@ class GarageDoorDialogHelper { ), title: 'Opening and Closing Time', onConfirm: () { - final updatedState = - context.read().state; - if (updatedState - is GarageDoorLoadedState) { + final updatedState = context.read().state; + if (updatedState is GarageDoorLoadedState) { context.read().add( GarageDoorControlEvent( - deviceId: - updatedState.status.uuid, + deviceId: updatedState.status.uuid, code: 'tr_timecon', - value: updatedState - .status.trTimeCon, + value: updatedState.status.trTimeCon, ), ); Navigator.pop(context); - // context.read().add( - // GarageDoorInitialEvent( - // bloc.deviceId)); } }); }, diff --git a/lib/pages/device_managment/garage_door/models/garage_door_model.dart b/lib/pages/device_managment/garage_door/models/garage_door_model.dart index dcb4718c..60d37d9f 100644 --- a/lib/pages/device_managment/garage_door/models/garage_door_model.dart +++ b/lib/pages/device_managment/garage_door/models/garage_door_model.dart @@ -65,7 +65,7 @@ class GarageDoorStatusModel { voiceControl1 = status.value ?? false; break; case 'door_state_1': - doorState1 = status.value ?? 'closed'; + doorState1 = status.value ?? 'close_time_alarm'; break; } } diff --git a/lib/pages/device_managment/main_door_sensor/widgets/notification_dialog.dart b/lib/pages/device_managment/main_door_sensor/widgets/notification_dialog.dart index d452026d..83ba41bc 100644 --- a/lib/pages/device_managment/main_door_sensor/widgets/notification_dialog.dart +++ b/lib/pages/device_managment/main_door_sensor/widgets/notification_dialog.dart @@ -2,9 +2,18 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class NotificationDialog extends StatelessWidget { +class NotificationDialog extends StatefulWidget { const NotificationDialog({super.key}); + @override + State createState() => _NotificationDialogState(); +} + +class _NotificationDialogState extends State { + bool isLowBatteryNotificationEnabled = true; + bool isClosingRemindersEnabled = true; + bool isDoorAlarmEnabled = true; + @override Widget build(BuildContext context) { return Dialog( @@ -66,7 +75,11 @@ class NotificationDialog extends StatelessWidget { code: 'notification', deviceId: '', label: 'Low Battery', - onChange: (v) {}, + onChange: (v) { + setState(() { + isLowBatteryNotificationEnabled = v; + }); + }, icon: '-1', ), ToggleWidget( @@ -74,7 +87,11 @@ class NotificationDialog extends StatelessWidget { code: 'notification', deviceId: '', label: 'Closing\nReminders', - onChange: (v) {}, + onChange: (v) { + setState(() { + isClosingRemindersEnabled = v; + }); + }, icon: '-1', ), ToggleWidget( @@ -82,7 +99,11 @@ class NotificationDialog extends StatelessWidget { code: 'notification', deviceId: '', label: 'Door Alarm', - onChange: (v) {}, + onChange: (v) { + setState(() { + isDoorAlarmEnabled = v; + }); + }, icon: '-1', ), ], diff --git a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart index b7760333..eb72eecd 100644 --- a/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart +++ b/lib/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart @@ -10,14 +10,12 @@ import 'package:syncrow_web/services/devices_mang_api.dart'; part 'one_gang_glass_switch_event.dart'; part 'one_gang_glass_switch_state.dart'; -class OneGangGlassSwitchBloc - extends Bloc { +class OneGangGlassSwitchBloc extends Bloc { OneGangGlassStatusModel deviceStatus; Timer? _timer; OneGangGlassSwitchBloc({required String deviceId}) - : deviceStatus = OneGangGlassStatusModel( - uuid: deviceId, switch1: false, countDown: 0), + : deviceStatus = OneGangGlassStatusModel(uuid: deviceId, switch1: false, countDown: 0), super(OneGangGlassSwitchInitial()) { on(_onFetchDeviceStatus); on(_onControl); @@ -26,22 +24,19 @@ class OneGangGlassSwitchBloc on(_onFactoryReset); } - Future _onFetchDeviceStatus(OneGangGlassSwitchFetchDeviceEvent event, - Emitter emit) async { + Future _onFetchDeviceStatus( + OneGangGlassSwitchFetchDeviceEvent event, Emitter emit) async { emit(OneGangGlassSwitchLoading()); try { - final status = - await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = - OneGangGlassStatusModel.fromJson(event.deviceId, status.status); + final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status); emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(OneGangGlassSwitchError(e.toString())); } } - Future _onControl(OneGangGlassSwitchControl event, - Emitter emit) async { + Future _onControl(OneGangGlassSwitchControl event, Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); @@ -57,12 +52,10 @@ class OneGangGlassSwitchBloc ); } - Future _onFactoryReset(OneGangGlassFactoryResetEvent event, - Emitter emit) async { + Future _onFactoryReset(OneGangGlassFactoryResetEvent event, Emitter emit) async { emit(OneGangGlassSwitchLoading()); try { - final response = await DevicesManagementApi() - .factoryReset(event.factoryReset, event.deviceId); + final response = await DevicesManagementApi().factoryReset(event.factoryReset, event.deviceId); if (!response) { emit(OneGangGlassSwitchError('Failed to reset device')); } else { @@ -73,12 +66,11 @@ class OneGangGlassSwitchBloc } } - Future _onBatchControl(OneGangGlassSwitchBatchControl event, - Emitter emit) async { + Future _onBatchControl(OneGangGlassSwitchBatchControl event, Emitter emit) async { final oldValue = _getValueByCode(event.code); _updateLocalValue(event.code, event.value); - emit(OneGangGlassSwitchBatchStatusLoaded(deviceStatus)); + emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); await _runDebounce( deviceId: event.deviceIds, @@ -91,15 +83,12 @@ class OneGangGlassSwitchBloc } Future _onFetchBatchStatus( - OneGangGlassSwitchFetchBatchStatusEvent event, - Emitter emit) async { + OneGangGlassSwitchFetchBatchStatusEvent event, Emitter emit) async { emit(OneGangGlassSwitchLoading()); try { - final status = - await DevicesManagementApi().getBatchStatus(event.deviceIds); - deviceStatus = OneGangGlassStatusModel.fromJson( - event.deviceIds.first, status.status); - emit(OneGangGlassSwitchBatchStatusLoaded(deviceStatus)); + final status = await DevicesManagementApi().getBatchStatus(event.deviceIds); + deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceIds.first, status.status); + emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); } catch (e) { emit(OneGangGlassSwitchError(e.toString())); } @@ -128,11 +117,9 @@ class OneGangGlassSwitchBloc try { late bool response; if (isBatch) { - response = await DevicesManagementApi() - .deviceBatchControl(deviceId, code, value); + response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); } else { - response = await DevicesManagementApi() - .deviceControl(deviceId, Status(code: code, value: value)); + response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); } if (!response) { @@ -144,8 +131,7 @@ class OneGangGlassSwitchBloc }); } - void _revertValueAndEmit(String deviceId, String code, bool oldValue, - Emitter emit) { + void _revertValueAndEmit(String deviceId, String code, bool oldValue, Emitter emit) { _updateLocalValue(code, oldValue); emit(OneGangGlassSwitchStatusLoaded(deviceStatus)); } diff --git a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart index 77b3dc9e..8914b786 100644 --- a/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart +++ b/lib/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart @@ -55,7 +55,7 @@ class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiv value: status.switch1, code: 'switch_1', deviceId: deviceId, - label: 'Wall LightÙ‹', + label: "Wall Light", onChange: (value) { context.read().add( OneGangGlassSwitchControl( diff --git a/lib/pages/device_managment/shared/toggle_widget.dart b/lib/pages/device_managment/shared/toggle_widget.dart index ad0ba8ad..61c16e4b 100644 --- a/lib/pages/device_managment/shared/toggle_widget.dart +++ b/lib/pages/device_managment/shared/toggle_widget.dart @@ -31,6 +31,7 @@ class ToggleWidget extends StatelessWidget { @override Widget build(BuildContext context) { + debugPrint(label.toString()); return DeviceControlsContainer( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart b/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart index f7118ae6..4d1bb91c 100644 --- a/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart +++ b/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_batch_control_view.dart @@ -57,7 +57,7 @@ class ThreeGangGlassSwitchBatchControlView extends StatelessWidget with HelperRe value: status.switch1, code: 'switch_1', deviceId: deviceIds.first, - label: 'Glass Switch 1', + label: "Wall Light", onChange: (value) { context.read().add( ThreeGangGlassSwitchBatchControl( @@ -72,7 +72,7 @@ class ThreeGangGlassSwitchBatchControlView extends StatelessWidget with HelperRe value: status.switch2, code: 'switch_2', deviceId: deviceIds.first, - label: 'Glass Switch 2', + label: "Ceiling Light", onChange: (value) { context.read().add( ThreeGangGlassSwitchBatchControl( @@ -87,7 +87,7 @@ class ThreeGangGlassSwitchBatchControlView extends StatelessWidget with HelperRe value: status.switch3, code: 'switch_3', deviceId: deviceIds.first, - label: 'Glass Switch 3', + label: "SpotLight", onChange: (value) { context.read().add( ThreeGangGlassSwitchBatchControl( diff --git a/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart b/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart index 2f14eaac..433e5408 100644 --- a/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart +++ b/lib/pages/device_managment/three_g_glass_switch/view/three_gang_glass_switch_control_view.dart @@ -56,7 +56,7 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget with HelperRespons value: status.switch1, code: 'switch_1', deviceId: deviceId, - label: 'Wall Light', + label: "Wall Light", onChange: (value) { context.read().add( ThreeGangGlassSwitchControl( @@ -71,7 +71,7 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget with HelperRespons value: status.switch2, code: 'switch_2', deviceId: deviceId, - label: 'Ceiling Light', + label: "Ceiling Light", onChange: (value) { context.read().add( ThreeGangGlassSwitchControl( @@ -86,7 +86,7 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget with HelperRespons value: status.switch3, code: 'switch_3', deviceId: deviceId, - label: 'SpotLight', + label: "SpotLight", onChange: (value) { context.read().add( ThreeGangGlassSwitchControl( diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart index 0a5b2895..498c55fb 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_bloc.dart @@ -1,6 +1,7 @@ // water_heater_bloc.dart import 'dart:async'; + import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; @@ -30,6 +31,7 @@ class WaterHeaterBloc extends Bloc { on(_getSchedule); on(_onAddSchedule); + on(_onEditSchedule); on(_onDeleteSchedule); on(_onUpdateSchedule); } @@ -76,8 +78,7 @@ class WaterHeaterBloc extends Bloc { final currentState = state as WaterHeaterDeviceStatusLoaded; final updatedDays = List.from(currentState.selectedDays); updatedDays[event.index] = event.value; - emit(currentState.copyWith( - selectedDays: updatedDays, selectedTime: currentState.selectedTime)); + emit(currentState.copyWith(selectedDays: updatedDays, selectedTime: currentState.selectedTime)); } FutureOr _updateFunctionOn( @@ -85,8 +86,7 @@ class WaterHeaterBloc extends Bloc { Emitter emit, ) { final currentState = state as WaterHeaterDeviceStatusLoaded; - emit(currentState.copyWith( - functionOn: event.isOn, selectedTime: currentState.selectedTime)); + emit(currentState.copyWith(functionOn: event.isOn, selectedTime: currentState.selectedTime)); } FutureOr _updateScheduleEvent( @@ -101,8 +101,7 @@ class WaterHeaterBloc extends Bloc { )); } if (event.scheduleMode == ScheduleModes.countdown) { - final countdownRemaining = - Duration(hours: event.hours, minutes: event.minutes); + final countdownRemaining = Duration(hours: event.hours, minutes: event.minutes); emit(currentState.copyWith( scheduleMode: ScheduleModes.countdown, @@ -112,13 +111,11 @@ class WaterHeaterBloc extends Bloc { countdownRemaining: countdownRemaining, )); - if (!currentState.isCountdownActive! && - countdownRemaining > Duration.zero) { + if (!currentState.isCountdownActive! && countdownRemaining > Duration.zero) { _startCountdownTimer(emit, countdownRemaining); } } else if (event.scheduleMode == ScheduleModes.inching) { - final inchingDuration = - Duration(hours: event.hours, minutes: event.minutes); + final inchingDuration = Duration(hours: event.hours, minutes: event.minutes); emit(currentState.copyWith( scheduleMode: ScheduleModes.inching, @@ -220,8 +217,7 @@ class WaterHeaterBloc extends Bloc { try { final status = await DevicesManagementApi().deviceControl( event.deviceId, - Status( - code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0), + Status(code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0), ); if (!status) { emit(const WaterHeaterFailedState(error: 'Failed to stop schedule.')); @@ -239,10 +235,8 @@ class WaterHeaterBloc extends Bloc { emit(WaterHeaterLoadingState()); try { - final status = - await DevicesManagementApi().getDeviceStatus(event.deviceId); - deviceStatus = - WaterHeaterStatusModel.fromJson(event.deviceId, status.status); + final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); + deviceStatus = WaterHeaterStatusModel.fromJson(event.deviceId, status.status); if (deviceStatus.scheduleMode == ScheduleModes.countdown) { final countdownRemaining = Duration( @@ -340,10 +334,8 @@ class WaterHeaterBloc extends Bloc { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; - if (currentState.countdownRemaining != null && - currentState.countdownRemaining! > Duration.zero) { - final newRemaining = - currentState.countdownRemaining! - const Duration(minutes: 1); + if (currentState.countdownRemaining != null && currentState.countdownRemaining! > Duration.zero) { + final newRemaining = currentState.countdownRemaining! - const Duration(minutes: 1); if (newRemaining <= Duration.zero) { _countdownTimer?.cancel(); @@ -438,8 +430,7 @@ class WaterHeaterBloc extends Bloc { } } - void _revertValue(String code, dynamic oldValue, - void Function(WaterHeaterState state) emit) { + void _revertValue(String code, dynamic oldValue, void Function(WaterHeaterState state) emit) { _updateLocalValue(code, oldValue); if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; @@ -486,13 +477,12 @@ class WaterHeaterBloc extends Bloc { return super.close(); } - FutureOr _getSchedule( - GetSchedulesEvent event, Emitter emit) async { + FutureOr _getSchedule(GetSchedulesEvent event, Emitter emit) async { emit(ScheduleLoadingState()); try { - List schedules = await DevicesManagementApi() - .getDeviceSchedules(deviceStatus.uuid, event.category); + List schedules = + await DevicesManagementApi().getDeviceSchedules(deviceStatus.uuid, event.category); emit(WaterHeaterDeviceStatusLoaded( deviceStatus, @@ -524,8 +514,35 @@ class WaterHeaterBloc extends Bloc { // emit(ScheduleLoadingState()); - bool success = await DevicesManagementApi() - .addScheduleRecord(newSchedule, currentState.status.uuid); + bool success = await DevicesManagementApi().addScheduleRecord(newSchedule, currentState.status.uuid); + + if (success) { + add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid)); + } else { + emit(currentState); + //emit(const WaterHeaterFailedState(error: 'Failed to add schedule.')); + } + } + } + + FutureOr _onEditSchedule(EditWaterHeaterScheduleEvent event, Emitter emit) async { + if (state is WaterHeaterDeviceStatusLoaded) { + final currentState = state as WaterHeaterDeviceStatusLoaded; + + ScheduleEntry newSchedule = ScheduleEntry( + scheduleId: event.scheduleId, + category: event.category, + time: formatTimeOfDayToISO(event.time), + function: Status(code: 'switch_1', value: event.functionOn), + days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays), + ); + + // emit(ScheduleLoadingState()); + + bool success = await DevicesManagementApi().editScheduleRecord( + currentState.status.uuid, + newSchedule, + ); if (success) { add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid)); @@ -577,13 +594,11 @@ class WaterHeaterBloc extends Bloc { // emit(ScheduleLoadingState()); - bool success = await DevicesManagementApi() - .deleteScheduleRecord(currentState.status.uuid, event.scheduleId); + bool success = await DevicesManagementApi().deleteScheduleRecord(currentState.status.uuid, event.scheduleId); if (success) { - final updatedSchedules = currentState.schedules - .where((schedule) => schedule.scheduleId != event.scheduleId) - .toList(); + final updatedSchedules = + currentState.schedules.where((schedule) => schedule.scheduleId != event.scheduleId).toList(); emit(currentState.copyWith(schedules: updatedSchedules)); } else { @@ -593,15 +608,12 @@ class WaterHeaterBloc extends Bloc { } } - FutureOr _batchFetchWaterHeater(FetchWaterHeaterBatchStatusEvent event, - Emitter emit) async { + FutureOr _batchFetchWaterHeater(FetchWaterHeaterBatchStatusEvent event, Emitter emit) async { emit(WaterHeaterLoadingState()); try { - final status = - await DevicesManagementApi().getBatchStatus(event.devicesUuid); - deviceStatus = WaterHeaterStatusModel.fromJson( - event.devicesUuid.first, status.status); + final status = await DevicesManagementApi().getBatchStatus(event.devicesUuid); + deviceStatus = WaterHeaterStatusModel.fromJson(event.devicesUuid.first, status.status); emit(WaterHeaterDeviceStatusLoaded(deviceStatus)); } catch (e) { @@ -609,8 +621,7 @@ class WaterHeaterBloc extends Bloc { } } - FutureOr _batchControlWaterHeater(ControlWaterHeaterBatchEvent event, - Emitter emit) async { + FutureOr _batchControlWaterHeater(ControlWaterHeaterBatchEvent event, Emitter emit) async { if (state is WaterHeaterDeviceStatusLoaded) { final currentState = state as WaterHeaterDeviceStatusLoaded; diff --git a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart index 4b9ec7a1..ff5de32c 100644 --- a/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart +++ b/lib/pages/device_managment/water_heater/bloc/water_heater_event.dart @@ -71,6 +71,22 @@ final class AddScheduleEvent extends WaterHeaterEvent { List get props => [selectedDays, time, functionOn, category]; } +class EditWaterHeaterScheduleEvent extends WaterHeaterEvent { + final String scheduleId; + final String category; + final TimeOfDay time; + final bool functionOn; + final List selectedDays; + + const EditWaterHeaterScheduleEvent({ + required this.scheduleId, + required this.category, + required this.time, + required this.functionOn, + required this.selectedDays, + }); +} + final class DeleteScheduleEvent extends WaterHeaterEvent { final int index; final String scheduleId; diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart index 4ccec509..9278e396 100644 --- a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -1,14 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -import 'package:syncrow_web/pages/common/buttons/default_button.dart'; class ScheduleDialogHelper { - static void showAddScheduleDialog(BuildContext context, - {ScheduleModel? schedule, int? index, bool? isEdit}) { + static void showAddScheduleDialog(BuildContext context, {ScheduleModel? schedule, int? index, bool? isEdit}) { final bloc = context.read(); if (schedule == null) { @@ -70,35 +69,30 @@ class ScheduleDialogHelper { padding: 8, backgroundColor: ColorsManager.boxColor, borderRadius: 15, - onPressed: isEdit == true - ? null - : () async { - TimeOfDay? time = await showTimePicker( - context: context, - initialTime: - state.selectedTime ?? TimeOfDay.now(), - builder: (context, child) { - return Theme( - data: Theme.of(context).copyWith( - colorScheme: const ColorScheme.light( - primary: ColorsManager.primaryColor, - ), - ), - child: child!, - ); - }, - ); - if (time != null) { - bloc.add(UpdateSelectedTimeEvent(time)); - } - }, + onPressed: () async { + TimeOfDay? time = await showTimePicker( + context: context, + initialTime: state.selectedTime ?? TimeOfDay.now(), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: const ColorScheme.light( + primary: ColorsManager.primaryColor, + ), + ), + child: child!, + ); + }, + ); + if (time != null) { + bloc.add(UpdateSelectedTimeEvent(time)); + } + }, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - state.selectedTime == null - ? 'Time' - : state.selectedTime!.format(context), + state.selectedTime == null ? 'Time' : state.selectedTime!.format(context), style: context.textTheme.bodySmall!.copyWith( color: ColorsManager.grayColor, ), @@ -113,8 +107,7 @@ class ScheduleDialogHelper { ), ), const SizedBox(height: 16), - _buildDayCheckboxes(context, state.selectedDays, - isEdit: isEdit), + _buildDayCheckboxes(context, state.selectedDays, isEdit: isEdit), const SizedBox(height: 16), _buildFunctionSwitch(context, state.functionOn, isEdit), ], @@ -141,7 +134,13 @@ class ScheduleDialogHelper { onPressed: () { if (state.selectedTime != null) { if (state.isEditing && index != null) { - return; + bloc.add(EditWaterHeaterScheduleEvent( + scheduleId: schedule?.scheduleId ?? '', + category: 'switch_1', + time: state.selectedTime!, + selectedDays: state.selectedDays, + functionOn: state.functionOn, + )); } else { bloc.add(AddScheduleEvent( category: 'switch_1', @@ -193,9 +192,7 @@ class ScheduleDialogHelper { return daysBoolean; } - static Widget _buildDayCheckboxes( - BuildContext context, List selectedDays, - {bool? isEdit}) { + static Widget _buildDayCheckboxes(BuildContext context, List selectedDays, {bool? isEdit}) { final dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; return Row( @@ -204,13 +201,9 @@ class ScheduleDialogHelper { children: [ Checkbox( value: selectedDays[index], - onChanged: isEdit == true - ? null - : (bool? value) { - context - .read() - .add(UpdateSelectedDayEvent(index, value!)); - }, + onChanged: (bool? value) { + context.read().add(UpdateSelectedDayEvent(index, value!)); + }, ), Text(dayLabels[index]), ], @@ -219,27 +212,19 @@ class ScheduleDialogHelper { ); } - static Widget _buildFunctionSwitch( - BuildContext context, bool isOn, bool? isEdit) { + static Widget _buildFunctionSwitch(BuildContext context, bool isOn, bool? isEdit) { return Row( children: [ Text( 'Function:', - style: context.textTheme.bodySmall! - .copyWith(color: ColorsManager.grayColor), + style: context.textTheme.bodySmall!.copyWith(color: ColorsManager.grayColor), ), const SizedBox(width: 10), Radio( value: true, groupValue: isOn, onChanged: (bool? value) { - if (isEdit == true) { - return; - } else { - context - .read() - .add(const UpdateFunctionOnEvent(true)); - } + context.read().add(const UpdateFunctionOnEvent(true)); }, ), const Text('On'), @@ -248,13 +233,7 @@ class ScheduleDialogHelper { value: false, groupValue: isOn, onChanged: (bool? value) { - if (isEdit == true) { - return; - } else { - context - .read() - .add(const UpdateFunctionOnEvent(false)); - } + context.read().add(const UpdateFunctionOnEvent(false)); }, ), const Text('Off'), diff --git a/lib/pages/device_managment/water_heater/models/schedule_entry.dart b/lib/pages/device_managment/water_heater/models/schedule_entry.dart index ca14bf51..a2a109af 100644 --- a/lib/pages/device_managment/water_heater/models/schedule_entry.dart +++ b/lib/pages/device_managment/water_heater/models/schedule_entry.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; - import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; class ScheduleEntry { @@ -9,12 +8,14 @@ class ScheduleEntry { final String time; final Status function; final List days; + final String? scheduleId; ScheduleEntry({ required this.category, required this.time, required this.function, required this.days, + this.scheduleId, }); @override @@ -38,6 +39,7 @@ class ScheduleEntry { Map toMap() { return { + 'scheduleId': scheduleId, 'category': category, 'time': time, 'function': function.toMap(), @@ -56,8 +58,7 @@ class ScheduleEntry { String toJson() => json.encode(toMap()); - factory ScheduleEntry.fromJson(String source) => - ScheduleEntry.fromMap(json.decode(source)); + factory ScheduleEntry.fromJson(String source) => ScheduleEntry.fromMap(json.decode(source)); @override bool operator ==(Object other) { @@ -72,9 +73,6 @@ class ScheduleEntry { @override int get hashCode { - return category.hashCode ^ - time.hashCode ^ - function.hashCode ^ - days.hashCode; + return category.hashCode ^ time.hashCode ^ function.hashCode ^ days.hashCode; } } diff --git a/lib/pages/device_managment/water_leak/widgets/water_leak_notifi_dialog.dart b/lib/pages/device_managment/water_leak/widgets/water_leak_notifi_dialog.dart index 0c2ae6ab..c8aca44e 100644 --- a/lib/pages/device_managment/water_leak/widgets/water_leak_notifi_dialog.dart +++ b/lib/pages/device_managment/water_leak/widgets/water_leak_notifi_dialog.dart @@ -2,9 +2,17 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class WaterLeakNotificationDialog extends StatelessWidget { +class WaterLeakNotificationDialog extends StatefulWidget { const WaterLeakNotificationDialog({super.key}); + @override + State createState() => _WaterLeakNotificationDialogState(); +} + +class _WaterLeakNotificationDialogState extends State { + bool isLowBatteryNotificationEnabled = true; + bool isWaterLeakageNotificationEnabled = true; + @override Widget build(BuildContext context) { return Dialog( @@ -66,7 +74,11 @@ class WaterLeakNotificationDialog extends StatelessWidget { code: 'notification', deviceId: '', label: 'Low Battery', - onChange: (v) {}, + onChange: (v) { + setState(() { + isLowBatteryNotificationEnabled = v; + }); + }, icon: '-1', ), ToggleWidget( @@ -74,7 +86,11 @@ class WaterLeakNotificationDialog extends StatelessWidget { code: 'notification', deviceId: '', label: 'Water Leakage', - onChange: (v) {}, + onChange: (v) { + setState(() { + isWaterLeakageNotificationEnabled = v; + }); + }, icon: '-1', ), ], diff --git a/lib/pages/visitor_password/model/device_model.dart b/lib/pages/visitor_password/model/device_model.dart index c357da52..c2f6f6f2 100644 --- a/lib/pages/visitor_password/model/device_model.dart +++ b/lib/pages/visitor_password/model/device_model.dart @@ -71,6 +71,12 @@ class DeviceModel { tempIcon = Assets.twoGang; } else if (type == DeviceType.WH) { tempIcon = Assets.waterHeater; + } else if (type == DeviceType.DoorSensor) { + tempIcon = Assets.sensors; + } else if (type == DeviceType.GarageDoor) { + tempIcon = Assets.openedDoor; + } else if (type == DeviceType.WaterLeak) { + tempIcon = Assets.waterLeakNormal; } else { tempIcon = Assets.blackLogo; } diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index dc0af9a8..bb591b0d 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -232,6 +232,22 @@ class DevicesManagementApi { } } + Future editScheduleRecord(String uuid, ScheduleEntry newSchedule) async { + try { + final response = await HTTPService().put( + path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), + body: newSchedule.toMap(), + expectedResponseModel: (json) { + return json['success'] ?? false; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return false; + } + } + Future deleteScheduleRecord(String uuid, String scheduleId) async { try { final response = await HTTPService().delete( diff --git a/lib/utils/enum/device_types.dart b/lib/utils/enum/device_types.dart index 9ddead07..2b1ce8a5 100644 --- a/lib/utils/enum/device_types.dart +++ b/lib/utils/enum/device_types.dart @@ -11,8 +11,30 @@ enum DeviceType { CeilingSensor, WallSensor, WH, + DoorSensor, + GarageDoor, + WaterLeak, Other, } +/* + + 3G: + 1G: + 2G: + GW: + DL: + WPS: + CPS: + AC: + CUR: + WH: + DS: + 1GT: + 2GT: + 3GT: + GD: + WL: + */ Map devicesTypesMap = { "AC": DeviceType.AC, @@ -25,4 +47,10 @@ Map devicesTypesMap = { "1G": DeviceType.OneGang, "CUR": DeviceType.Curtain, "WH": DeviceType.WH, + 'DS': DeviceType.DoorSensor, + "1GT": DeviceType.OneGang, + "2GT": DeviceType.TwoGang, + "3GT": DeviceType.ThreeGang, + 'GD': DeviceType.GarageDoor, + 'WL': DeviceType.WaterLeak }; From 03dfeabde40c1162ee56df375937c20ab6c168f3 Mon Sep 17 00:00:00 2001 From: Abdullah Alassaf Date: Tue, 8 Oct 2024 23:14:41 +0300 Subject: [PATCH 65/65] Bug fixes --- .../access_management/bloc/access_bloc.dart | 73 ++++---- lib/pages/auth/bloc/auth_bloc.dart | 54 +++--- .../bloc/device_managment_bloc.dart | 8 +- .../bloc/{bloc.dart => ceiling_bloc.dart} | 27 +-- .../bloc/{event.dart => ceiling_event.dart} | 0 .../bloc/{state.dart => ceiling_state.dart} | 0 .../model/ceiling_sensor_model.dart | 19 +-- .../view/ceiling_sensor_batch_control.dart | 23 ++- .../view/ceiling_sensor_controls.dart | 40 ++--- .../shared/table/report_table.dart | 161 ++++++++++-------- .../shared/toggle_widget.dart | 1 - .../bloc/{bloc.dart => wall_bloc.dart} | 29 ++-- .../bloc/{event.dart => wall_event.dart} | 0 .../bloc/{state.dart => wall_state.dart} | 0 .../view/wall_sensor_batch_control.dart | 33 ++-- .../view/wall_sensor_conrtols.dart | 51 +++--- .../bloc/visitor_password_bloc.dart | 4 +- .../visitor_password/model/device_model.dart | 2 +- lib/services/access_mang_api.dart | 1 - 19 files changed, 254 insertions(+), 272 deletions(-) rename lib/pages/device_managment/ceiling_sensor/bloc/{bloc.dart => ceiling_bloc.dart} (86%) rename lib/pages/device_managment/ceiling_sensor/bloc/{event.dart => ceiling_event.dart} (100%) rename lib/pages/device_managment/ceiling_sensor/bloc/{state.dart => ceiling_state.dart} (100%) rename lib/pages/device_managment/wall_sensor/bloc/{bloc.dart => wall_bloc.dart} (87%) rename lib/pages/device_managment/wall_sensor/bloc/{event.dart => wall_event.dart} (100%) rename lib/pages/device_managment/wall_sensor/bloc/{state.dart => wall_state.dart} (100%) diff --git a/lib/pages/access_management/bloc/access_bloc.dart b/lib/pages/access_management/bloc/access_bloc.dart index 4b7e499c..26b3efc4 100644 --- a/lib/pages/access_management/bloc/access_bloc.dart +++ b/lib/pages/access_management/bloc/access_bloc.dart @@ -64,13 +64,15 @@ class AccessBloc extends Bloc { } } - - Future selectTime(SelectTime event, Emitter emit,) async { + Future selectTime( + SelectTime event, + Emitter emit, + ) async { emit(AccessLoaded()); final DateTime? picked = await showDatePicker( context: event.context, initialDate: DateTime.now(), - firstDate: DateTime.now().add(const Duration(days: -5095)), + firstDate: DateTime.now().add(const Duration(days: -5095)), lastDate: DateTime.now().add(const Duration(days: 2095)), ); if (picked != null) { @@ -97,7 +99,8 @@ class AccessBloc extends Bloc { } } else { if (effectiveTimeTimeStamp != null && selectedTimestamp < effectiveTimeTimeStamp!) { - CustomSnackBar.displaySnackBar('Expiration Time cannot be earlier than Effective Time.'); + CustomSnackBar.displaySnackBar( + 'Expiration Time cannot be earlier than Effective Time.'); } else { endTime = selectedDateTime.toString().split('.').first; expirationTimeTimeStamp = selectedTimestamp; @@ -106,14 +109,11 @@ class AccessBloc extends Bloc { } } emit(ChangeTimeState()); - } - Future _filterData(FilterDataEvent event, Emitter emit) async { emit(AccessLoaded()); try { - print(event.emailAuthorizer?.toLowerCase()); // Convert search text to lower case for case-insensitive search final searchText = event.passwordName?.toLowerCase() ?? ''; final searchEmailText = event.emailAuthorizer?.toLowerCase() ?? ''; @@ -121,15 +121,17 @@ class AccessBloc extends Bloc { bool matchesCriteria = true; // Convert timestamp to DateTime and extract date component DateTime effectiveDate = - DateTime.fromMillisecondsSinceEpoch(int.parse(item.effectiveTime.toString()) * 1000) - .toUtc() - .toLocal(); + DateTime.fromMillisecondsSinceEpoch(int.parse(item.effectiveTime.toString()) * 1000) + .toUtc() + .toLocal(); DateTime invalidDate = - DateTime.fromMillisecondsSinceEpoch(int.parse(item.invalidTime.toString()) * 1000) - .toUtc() - .toLocal(); - DateTime effectiveDateAndTime = DateTime(effectiveDate.year, effectiveDate.month, effectiveDate.day,effectiveDate.hour,effectiveDate.minute); - DateTime invalidDateAndTime = DateTime(invalidDate.year, invalidDate.month, invalidDate.day,invalidDate.hour,invalidDate.minute); + DateTime.fromMillisecondsSinceEpoch(int.parse(item.invalidTime.toString()) * 1000) + .toUtc() + .toLocal(); + DateTime effectiveDateAndTime = DateTime(effectiveDate.year, effectiveDate.month, + effectiveDate.day, effectiveDate.hour, effectiveDate.minute); + DateTime invalidDateAndTime = DateTime(invalidDate.year, invalidDate.month, invalidDate.day, + invalidDate.hour, invalidDate.minute); // Filter by password name, making the search case-insensitive if (searchText.isNotEmpty) { @@ -139,7 +141,8 @@ class AccessBloc extends Bloc { } } if (searchEmailText.isNotEmpty) { - final bool matchesName = item.authorizerEmail.toString().toLowerCase().contains(searchEmailText); + final bool matchesName = + item.authorizerEmail.toString().toLowerCase().contains(searchEmailText); if (!matchesName) { matchesCriteria = false; } @@ -147,13 +150,9 @@ class AccessBloc extends Bloc { // Filter by start date only if (event.startTime != null && event.endTime == null) { DateTime startDateTime = - DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal(); - startDateTime = DateTime( - startDateTime.year, - startDateTime.month, - startDateTime.day, - startDateTime.hour, - startDateTime.minute); + DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal(); + startDateTime = DateTime(startDateTime.year, startDateTime.month, startDateTime.day, + startDateTime.hour, startDateTime.minute); if (effectiveDateAndTime.isBefore(startDateTime)) { matchesCriteria = false; } @@ -161,14 +160,9 @@ class AccessBloc extends Bloc { // Filter by end date only if (event.endTime != null && event.startTime == null) { DateTime startDateTime = - DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal(); - startDateTime = DateTime( - startDateTime.year, - startDateTime.month, - startDateTime.day, - startDateTime.hour, - startDateTime.minute - ); + DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal(); + startDateTime = DateTime(startDateTime.year, startDateTime.month, startDateTime.day, + startDateTime.hour, startDateTime.minute); if (invalidDateAndTime.isAfter(startDateTime)) { matchesCriteria = false; } @@ -177,12 +171,15 @@ class AccessBloc extends Bloc { // Filter by both start date and end date if (event.startTime != null && event.endTime != null) { DateTime startDateTime = - DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal(); + DateTime.fromMillisecondsSinceEpoch(event.startTime! * 1000).toUtc().toLocal(); DateTime endDateTime = - DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal(); - startDateTime = DateTime(startDateTime.year, startDateTime.month, startDateTime.day,startDateTime.hour,startDateTime.minute); - endDateTime = DateTime(endDateTime.year, endDateTime.month, endDateTime.day,endDateTime.hour,endDateTime.minute); - if (effectiveDateAndTime.isBefore(startDateTime) || invalidDateAndTime.isAfter(endDateTime)) { + DateTime.fromMillisecondsSinceEpoch(event.endTime! * 1000).toUtc().toLocal(); + startDateTime = DateTime(startDateTime.year, startDateTime.month, startDateTime.day, + startDateTime.hour, startDateTime.minute); + endDateTime = DateTime(endDateTime.year, endDateTime.month, endDateTime.day, + endDateTime.hour, endDateTime.minute); + if (effectiveDateAndTime.isBefore(startDateTime) || + invalidDateAndTime.isAfter(endDateTime)) { matchesCriteria = false; } } @@ -205,7 +202,6 @@ class AccessBloc extends Bloc { } } - resetSearch(ResetSearch event, Emitter emit) async { emit(AccessLoaded()); startTime = 'Start Time'; @@ -224,7 +220,6 @@ class AccessBloc extends Bloc { " ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}"; } - Future onTabChanged(TabChangedEvent event, Emitter emit) async { try { emit(AccessLoaded()); @@ -257,6 +252,4 @@ class AccessBloc extends Bloc { emit(FailedState(e.toString())); } } - } - diff --git a/lib/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart index cf01a73c..a8a32f54 100644 --- a/lib/pages/auth/bloc/auth_bloc.dart +++ b/lib/pages/auth/bloc/auth_bloc.dart @@ -96,35 +96,33 @@ class AuthBloc extends Bloc { Future changePassword(ChangePasswordEvent event, Emitter emit) async { emit(LoadingForgetState()); - try { - var response = await AuthenticationAPI.verifyOtp( - email: forgetEmailController.text, otpCode: forgetOtp.text); - if (response == true) { - await AuthenticationAPI.forgetPassword( - password: forgetPasswordController.text, - email: forgetEmailController.text); - _timer?.cancel(); - emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); - emit(SuccessForgetState()); - } - } on DioException catch (e) { - final errorData = e.response!.data; - String errorMessage = errorData['message']; - if (errorMessage == 'this email is not registered') { - validate = 'Invalid Credentials!'; - emit(AuthInitialState()); - } else if (errorMessage == "You entered wrong otp") { - forgetValidate = 'Wrong one time password.'; - emit(AuthInitialState()); - } else if (errorMessage == "OTP expired") { - forgetValidate = 'One time password has been expired.'; - emit(AuthInitialState()); - } else { - validate = ''; - emit(AuthInitialState()); - } + try { + var response = await AuthenticationAPI.verifyOtp( + email: forgetEmailController.text, otpCode: forgetOtp.text); + if (response == true) { + await AuthenticationAPI.forgetPassword( + password: forgetPasswordController.text, email: forgetEmailController.text); + _timer?.cancel(); + emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); + emit(SuccessForgetState()); } - + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + if (errorMessage == 'this email is not registered') { + validate = 'Invalid Credentials!'; + emit(AuthInitialState()); + } else if (errorMessage == "You entered wrong otp") { + forgetValidate = 'Wrong one time password.'; + emit(AuthInitialState()); + } else if (errorMessage == "OTP expired") { + forgetValidate = 'One time password has been expired.'; + emit(AuthInitialState()); + } else { + validate = ''; + emit(AuthInitialState()); + } + } } String? validateCode(String? value) { diff --git a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart index 46eedaaf..94659941 100644 --- a/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart +++ b/lib/pages/device_managment/all_devices/bloc/device_managment_bloc.dart @@ -220,7 +220,8 @@ class DeviceManagementBloc extends Bloc device.online == true).length; _offlineCount = _devices.where((device) => device.online == false).length; - _lowBatteryCount = _devices.where((device) => device.batteryLevel != null && device.batteryLevel! < 20).length; + _lowBatteryCount = + _devices.where((device) => device.batteryLevel != null && device.batteryLevel! < 20).length; } String _getFilterFromIndex(int index) { @@ -253,6 +254,8 @@ class DeviceManagementBloc extends Bloc { on(_onFactoryReset); } - void _fetchCeilingSensorStatus(CeilingInitialEvent event, Emitter emit) async { + void _fetchCeilingSensorStatus( + CeilingInitialEvent event, Emitter emit) async { emit(CeilingLoadingInitialState()); try { var response = await DevicesManagementApi().getDeviceStatus(event.deviceId); @@ -77,7 +78,8 @@ class CeilingSensorBloc extends Bloc { ); } - Future _onBatchControl(CeilingBatchControlEvent event, Emitter emit) async { + Future _onBatchControl( + CeilingBatchControlEvent event, Emitter emit) async { emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus)); if (event.code == 'sensitivity') { deviceStatus.sensitivity = event.value; @@ -122,7 +124,8 @@ class CeilingSensorBloc extends Bloc { if (isBatch) { response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); } else { - response = await DevicesManagementApi().deviceControl(deviceId, Status(code: code, value: value)); + response = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); } if (!response) { @@ -140,18 +143,19 @@ class CeilingSensorBloc extends Bloc { }); } - FutureOr _getDeviceReports(GetCeilingDeviceReportsEvent event, Emitter emit) async { + FutureOr _getDeviceReports( + GetCeilingDeviceReportsEvent event, Emitter emit) async { if (event.code.isEmpty) { emit(ShowCeilingDescriptionState(description: reportString)); return; } else { emit(CeilingReportsLoadingState()); - final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch; - final to = DateTime.now().millisecondsSinceEpoch; + // final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch; + // final to = DateTime.now().millisecondsSinceEpoch; try { - await DevicesManagementApi.getDeviceReportsByDate(deviceId, event.code, from.toString(), to.toString()) - .then((value) { + // await DevicesManagementApi.getDeviceReportsByDate(deviceId, event.code, from.toString(), to.toString()) + await DevicesManagementApi.getDeviceReports(deviceId, event.code).then((value) { emit(CeilingReportsState(deviceReport: value)); }); } catch (e) { @@ -182,7 +186,8 @@ class CeilingSensorBloc extends Bloc { } } - FutureOr _onFactoryReset(CeilingFactoryResetEvent event, Emitter emit) async { + FutureOr _onFactoryReset( + CeilingFactoryResetEvent event, Emitter emit) async { emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus)); try { final response = await DevicesManagementApi().factoryReset( diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/event.dart b/lib/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart similarity index 100% rename from lib/pages/device_managment/ceiling_sensor/bloc/event.dart rename to lib/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart diff --git a/lib/pages/device_managment/ceiling_sensor/bloc/state.dart b/lib/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart similarity index 100% rename from lib/pages/device_managment/ceiling_sensor/bloc/state.dart rename to lib/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart diff --git a/lib/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart b/lib/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart index 18149990..08a65a11 100644 --- a/lib/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart +++ b/lib/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart @@ -46,22 +46,19 @@ class CeilingSensorModel { _spaceType = getSpaceType(status.value ?? 'none'); break; case 'sensitivity': - _sensitivity = status.value is int - ? status.value - : int.tryParse(status.value ?? '1') ?? 1; + _sensitivity = + status.value is int ? status.value : int.tryParse(status.value ?? '1') ?? 1; break; case 'checking_result': _checkingResult = status.value ?? ''; break; case 'presence_range': - _presenceRange = status.value is int - ? status.value - : int.tryParse(status.value ?? '0') ?? 0; + _presenceRange = + status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0; break; case 'sports_para': - _sportsPara = status.value is int - ? status.value - : int.tryParse(status.value ?? '0') ?? 0; + _sportsPara = + status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0; break; case 'body_movement': _bodyMovement = status.value ?? ''; @@ -70,9 +67,7 @@ class CeilingSensorModel { _noBodyTime = status.value ?? 'none'; break; case 'moving_max_dis': - _maxDis = status.value is int - ? status.value - : int.tryParse(status.value ?? '0') ?? 0; + _maxDis = status.value is int ? status.value : int.tryParse(status.value ?? '0') ?? 0; break; } } diff --git a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart index d1dc54a2..b7f9af5d 100644 --- a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart +++ b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_batch_control.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; -import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/bloc.dart'; -import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/event.dart'; -import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/state.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; @@ -12,8 +12,7 @@ import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presen import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class CeilingSensorBatchControlView extends StatelessWidget - with HelperResponsiveLayout { +class CeilingSensorBatchControlView extends StatelessWidget with HelperResponsiveLayout { const CeilingSensorBatchControlView({super.key, required this.devicesIds}); final List devicesIds; @@ -28,12 +27,11 @@ class CeilingSensorBatchControlView extends StatelessWidget ..add(CeilingFetchDeviceStatusEvent(devicesIds)), child: BlocBuilder( builder: (context, state) { - if (state is CeilingLoadingInitialState || - state is CeilingReportsLoadingState) { + if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) { return const Center(child: CircularProgressIndicator()); } else if (state is CeilingUpdateState) { - return _buildGridView(context, state.ceilingSensorModel, - isExtraLarge, isLarge, isMedium); + return _buildGridView( + context, state.ceilingSensorModel, isExtraLarge, isLarge, isMedium); } return const Center(child: Text('Error fetching status')); }, @@ -41,8 +39,8 @@ class CeilingSensorBatchControlView extends StatelessWidget ); } - Widget _buildGridView(BuildContext context, CeilingSensorModel model, - bool isExtraLarge, bool isLarge, bool isMedium) { + Widget _buildGridView(BuildContext context, CeilingSensorModel model, bool isExtraLarge, + bool isLarge, bool isMedium) { return GridView( padding: const EdgeInsets.symmetric(horizontal: 50), shrinkWrap: true, @@ -118,8 +116,7 @@ class CeilingSensorBatchControlView extends StatelessWidget context.read().add( CeilingFactoryResetEvent( devicesId: devicesIds.first, - factoryResetModel: - FactoryResetModel(devicesUuid: devicesIds), + factoryResetModel: FactoryResetModel(devicesUuid: devicesIds), ), ); }, diff --git a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart index ecf0afae..845c326b 100644 --- a/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart +++ b/lib/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; -import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/bloc.dart'; -import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/event.dart'; -import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/state.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart'; +import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart'; @@ -16,8 +16,7 @@ import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dar import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class CeilingSensorControlsView extends StatelessWidget - with HelperResponsiveLayout { +class CeilingSensorControlsView extends StatelessWidget with HelperResponsiveLayout { const CeilingSensorControlsView({super.key, required this.device}); final AllDevicesModel device; @@ -32,35 +31,29 @@ class CeilingSensorControlsView extends StatelessWidget ..add(CeilingInitialEvent(device.uuid ?? '')), child: BlocBuilder( builder: (context, state) { - if (state is CeilingLoadingInitialState || - state is CeilingReportsLoadingState) { + if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) { return const Center(child: CircularProgressIndicator()); } else if (state is CeilingUpdateState) { - return _buildGridView(context, state.ceilingSensorModel, - isExtraLarge, isLarge, isMedium); + return _buildGridView( + context, state.ceilingSensorModel, isExtraLarge, isLarge, isMedium); } else if (state is CeilingReportsState) { return ReportsTable( report: state.deviceReport, onRowTap: (index) {}, onClose: () { - context - .read() - .add(BackToCeilingGridViewEvent()); + context.read().add(BackToCeilingGridViewEvent()); }, ); } else if (state is ShowCeilingDescriptionState) { return DescriptionView( description: state.description, onClose: () { - context - .read() - .add(BackToCeilingGridViewEvent()); + context.read().add(BackToCeilingGridViewEvent()); }, ); } else if (state is CeilingReportsFailedState) { final model = context.read().deviceStatus; - return _buildGridView( - context, model, isExtraLarge, isLarge, isMedium); + return _buildGridView(context, model, isExtraLarge, isLarge, isMedium); } return const Center(child: Text('Error fetching status')); }, @@ -68,8 +61,8 @@ class CeilingSensorControlsView extends StatelessWidget ); } - Widget _buildGridView(BuildContext context, CeilingSensorModel model, - bool isExtraLarge, bool isLarge, bool isMedium) { + Widget _buildGridView(BuildContext context, CeilingSensorModel model, bool isExtraLarge, + bool isLarge, bool isMedium) { return GridView( padding: const EdgeInsets.symmetric(horizontal: 50), shrinkWrap: true, @@ -150,8 +143,8 @@ class CeilingSensorControlsView extends StatelessWidget ), GestureDetector( onTap: () { - context.read().add(GetCeilingDeviceReportsEvent( - code: 'presence_state', deviceUuid: device.uuid!)); + context.read().add( + GetCeilingDeviceReportsEvent(code: 'presence_state', deviceUuid: device.uuid!)); }, child: const PresenceStaticWidget( icon: Assets.illuminanceRecordIcon, @@ -160,8 +153,9 @@ class CeilingSensorControlsView extends StatelessWidget ), GestureDetector( onTap: () { - context.read().add(GetCeilingDeviceReportsEvent( - code: '', deviceUuid: device.uuid!)); + context + .read() + .add(GetCeilingDeviceReportsEvent(code: '', deviceUuid: device.uuid!)); }, child: const PresenceStaticWidget( icon: Assets.helpDescriptionIcon, diff --git a/lib/pages/device_managment/shared/table/report_table.dart b/lib/pages/device_managment/shared/table/report_table.dart index 892bdd1a..11385080 100644 --- a/lib/pages/device_managment/shared/table/report_table.dart +++ b/lib/pages/device_managment/shared/table/report_table.dart @@ -3,6 +3,8 @@ import 'package:intl/intl.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/table_cell_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/table_header.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; // ignore: must_be_immutable class ReportsTable extends StatelessWidget { @@ -31,81 +33,90 @@ class ReportsTable extends StatelessWidget { @override Widget build(BuildContext context) { - return Stack( - children: [ - Padding( - padding: const EdgeInsets.all(20.0), - child: Table( - border: TableBorder.all(color: Colors.grey.shade300, width: 1), - columnWidths: const { - 0: FlexColumnWidth(), - 1: FlexColumnWidth(), - 2: FlexColumnWidth(), - }, - children: [ - TableRow( - decoration: BoxDecoration(color: Colors.grey.shade200), - children: [ - const TableHeader(title: 'Date'), - const TableHeader(title: 'Time'), - TableHeader(title: thirdColumnTitle ?? 'Status'), - ], - ), - if (report.data != null) - ...report.data!.asMap().entries.map((entry) { - int index = entry.key; - DeviceEvent data = entry.value; - - // Parse eventTime into Date and Time - DateTime eventDateTime = - DateTime.fromMillisecondsSinceEpoch(data.eventTime!); - String date = DateFormat('dd/MM/yyyy').format(eventDateTime); - String time = DateFormat('HH:mm').format(eventDateTime); - - String value; - if (hideValueShowDescription == true) { - if (mainDoorSensor != null && mainDoorSensor == true) { - value = data.value == 'true' ? 'Open' : 'Close'; - } else if (garageDoorSensor != null && - garageDoorSensor == true) { - value = data.value == 'true' ? 'Opened' : 'Closed'; - } else if (waterLeak != null && waterLeak == true) { - value = - data.value == 'normal' ? 'Normal' : 'Leak Detected'; - } else { - value = '${data.value!} ${thirdColumnDescription ?? ''}'; - } - } else { - value = '${data.value!} ${thirdColumnDescription ?? ''}'; - } - - return TableRow( - children: [ - TableCellWidget(value: date), - TableCellWidget(value: time), - TableCellWidget( - value: value, - onTap: () => onRowTap(index), - ), - ], - ); - }), - ], - ), - ), - Positioned( - top: 0, - right: 0, - child: IconButton( - icon: const Icon( - Icons.close, - color: Colors.red, - size: 18, + return report.data == null || report.data!.isEmpty + ? Container( + padding: const EdgeInsets.all(20.0), + width: MediaQuery.sizeOf(context).width, + alignment: AlignmentDirectional.center, + height: 100, + child: Text( + 'No reports found', + style: context.textTheme.bodyLarge!.copyWith(color: ColorsManager.grayColor), ), - onPressed: onClose, - ), - ), - ], - ); + ) + : Stack( + children: [ + Padding( + padding: const EdgeInsets.all(20.0), + child: Table( + border: TableBorder.all(color: Colors.grey.shade300, width: 1), + columnWidths: const { + 0: FlexColumnWidth(), + 1: FlexColumnWidth(), + 2: FlexColumnWidth(), + }, + children: [ + TableRow( + decoration: BoxDecoration(color: Colors.grey.shade200), + children: [ + const TableHeader(title: 'Date'), + const TableHeader(title: 'Time'), + TableHeader(title: thirdColumnTitle ?? 'Status'), + ], + ), + if (report.data != null) + ...report.data!.asMap().entries.map((entry) { + int index = entry.key; + DeviceEvent data = entry.value; + + // Parse eventTime into Date and Time + DateTime eventDateTime = + DateTime.fromMillisecondsSinceEpoch(data.eventTime!); + String date = DateFormat('dd/MM/yyyy').format(eventDateTime); + String time = DateFormat('HH:mm').format(eventDateTime); + + String value; + if (hideValueShowDescription == true) { + if (mainDoorSensor != null && mainDoorSensor == true) { + value = data.value == 'true' ? 'Open' : 'Close'; + } else if (garageDoorSensor != null && garageDoorSensor == true) { + value = data.value == 'true' ? 'Opened' : 'Closed'; + } else if (waterLeak != null && waterLeak == true) { + value = data.value == 'normal' ? 'Normal' : 'Leak Detected'; + } else { + value = '${data.value!} ${thirdColumnDescription ?? ''}'; + } + } else { + value = '${data.value!} ${thirdColumnDescription ?? ''}'; + } + + return TableRow( + children: [ + TableCellWidget(value: date), + TableCellWidget(value: time), + TableCellWidget( + value: value, + onTap: () => onRowTap(index), + ), + ], + ); + }) + ], + ), + ), + Positioned( + top: 0, + right: 0, + child: IconButton( + icon: const Icon( + Icons.close, + color: Colors.red, + size: 18, + ), + onPressed: onClose, + ), + ), + ], + ); } } diff --git a/lib/pages/device_managment/shared/toggle_widget.dart b/lib/pages/device_managment/shared/toggle_widget.dart index 61c16e4b..ad0ba8ad 100644 --- a/lib/pages/device_managment/shared/toggle_widget.dart +++ b/lib/pages/device_managment/shared/toggle_widget.dart @@ -31,7 +31,6 @@ class ToggleWidget extends StatelessWidget { @override Widget build(BuildContext context) { - debugPrint(label.toString()); return DeviceControlsContainer( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/pages/device_managment/wall_sensor/bloc/bloc.dart b/lib/pages/device_managment/wall_sensor/bloc/wall_bloc.dart similarity index 87% rename from lib/pages/device_managment/wall_sensor/bloc/bloc.dart rename to lib/pages/device_managment/wall_sensor/bloc/wall_bloc.dart index f3040b48..41598439 100644 --- a/lib/pages/device_managment/wall_sensor/bloc/bloc.dart +++ b/lib/pages/device_managment/wall_sensor/bloc/wall_bloc.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; -import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/event.dart'; -import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/state.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_state.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; import 'package:syncrow_web/services/devices_mang_api.dart'; @@ -38,12 +38,10 @@ class WallSensorBloc extends Bloc { // Fetch batch status FutureOr _fetchWallSensorBatchControl( - WallSensorFetchBatchStatusEvent event, - Emitter emit) async { + WallSensorFetchBatchStatusEvent event, Emitter emit) async { emit(WallSensorLoadingInitialState()); try { - var response = - await DevicesManagementApi().getBatchStatus(event.devicesIds); + var response = await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = WallSensorModel.fromJson(response.status); emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); } catch (e) { @@ -70,8 +68,7 @@ class WallSensorBloc extends Bloc { // } catch (_) {} // } - void _changeValue( - WallSensorChangeValueEvent event, Emitter emit) async { + void _changeValue(WallSensorChangeValueEvent event, Emitter emit) async { emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus)); if (event.code == 'far_detection') { deviceStatus.farDetection = event.value; @@ -128,8 +125,7 @@ class WallSensorBloc extends Bloc { try { late bool response; if (isBatch) { - response = await DevicesManagementApi() - .deviceBatchControl(deviceId, code, value); + response = await DevicesManagementApi().deviceBatchControl(deviceId, code, value); } else { response = await DevicesManagementApi() .deviceControl(deviceId, Status(code: code, value: value)); @@ -148,10 +144,13 @@ class WallSensorBloc extends Bloc { FutureOr _getDeviceReports( GetDeviceReportsEvent event, Emitter emit) async { emit(DeviceReportsLoadingState()); + // final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch; + // final to = DateTime.now().millisecondsSinceEpoch; try { - await DevicesManagementApi.getDeviceReports(deviceId, event.code) - .then((value) { + // await DevicesManagementApi.getDeviceReportsByDate( + // deviceId, event.code, from.toString(), to.toString()) + await DevicesManagementApi.getDeviceReports(deviceId, event.code).then((value) { emit(DeviceReportsState(deviceReport: value, code: event.code)); }); } catch (e) { @@ -160,13 +159,11 @@ class WallSensorBloc extends Bloc { } } - void _showDescription( - ShowDescriptionEvent event, Emitter emit) { + void _showDescription(ShowDescriptionEvent event, Emitter emit) { emit(WallSensorShowDescriptionState(description: event.description)); } - void _backToGridView( - BackToGridViewEvent event, Emitter emit) { + void _backToGridView(BackToGridViewEvent event, Emitter emit) { emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); } diff --git a/lib/pages/device_managment/wall_sensor/bloc/event.dart b/lib/pages/device_managment/wall_sensor/bloc/wall_event.dart similarity index 100% rename from lib/pages/device_managment/wall_sensor/bloc/event.dart rename to lib/pages/device_managment/wall_sensor/bloc/wall_event.dart diff --git a/lib/pages/device_managment/wall_sensor/bloc/state.dart b/lib/pages/device_managment/wall_sensor/bloc/wall_state.dart similarity index 100% rename from lib/pages/device_managment/wall_sensor/bloc/state.dart rename to lib/pages/device_managment/wall_sensor/bloc/wall_state.dart diff --git a/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart index 66078d60..66ff67aa 100644 --- a/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart +++ b/lib/pages/device_managment/wall_sensor/view/wall_sensor_batch_control.dart @@ -4,14 +4,13 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; -import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/bloc.dart'; -import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/event.dart'; -import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/state.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_state.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class WallSensorBatchControlView extends StatelessWidget - with HelperResponsiveLayout { +class WallSensorBatchControlView extends StatelessWidget with HelperResponsiveLayout { const WallSensorBatchControlView({super.key, required this.devicesIds}); final List devicesIds; @@ -26,16 +25,13 @@ class WallSensorBatchControlView extends StatelessWidget ..add(WallSensorFetchBatchStatusEvent(devicesIds)), child: BlocBuilder( builder: (context, state) { - if (state is WallSensorLoadingInitialState || - state is DeviceReportsLoadingState) { + if (state is WallSensorLoadingInitialState || state is DeviceReportsLoadingState) { return const Center(child: CircularProgressIndicator()); } else if (state is WallSensorUpdateState) { - return _buildGridView(context, state.wallSensorModel, isExtraLarge, - isLarge, isMedium); + return _buildGridView(context, state.wallSensorModel, isExtraLarge, isLarge, isMedium); } else if (state is DeviceReportsFailedState) { final model = context.read().deviceStatus; - return _buildGridView( - context, model, isExtraLarge, isLarge, isMedium); + return _buildGridView(context, model, isExtraLarge, isLarge, isMedium); } return const Center(child: Text('Error fetching status')); }, @@ -43,8 +39,8 @@ class WallSensorBatchControlView extends StatelessWidget ); } - Widget _buildGridView(BuildContext context, WallSensorModel model, - bool isExtraLarge, bool isLarge, bool isMedium) { + Widget _buildGridView( + BuildContext context, WallSensorModel model, bool isExtraLarge, bool isLarge, bool isMedium) { return GridView( padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20), shrinkWrap: true, @@ -97,12 +93,11 @@ class WallSensorBatchControlView extends StatelessWidget maxValue: 10000, steps: 1, description: 'sec', - action: (int value) => - context.read().add(WallSensorBatchControlEvent( - deviceIds: devicesIds, - code: 'no_one_time', - value: value, - ))), + action: (int value) => context.read().add(WallSensorBatchControlEvent( + deviceIds: devicesIds, + code: 'no_one_time', + value: value, + ))), PresenceUpdateData( value: model.farDetection.toDouble(), title: 'Far Detection:', diff --git a/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart b/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart index f0ff7591..370edaa5 100644 --- a/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart +++ b/lib/pages/device_managment/wall_sensor/view/wall_sensor_conrtols.dart @@ -3,9 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/description_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart'; -import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/bloc.dart'; -import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/event.dart'; -import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/state.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart'; +import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_state.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_status.dart'; @@ -14,8 +14,7 @@ import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; -class WallSensorControlsView extends StatelessWidget - with HelperResponsiveLayout { +class WallSensorControlsView extends StatelessWidget with HelperResponsiveLayout { const WallSensorControlsView({super.key, required this.device}); final AllDevicesModel device; @@ -26,23 +25,19 @@ class WallSensorControlsView extends StatelessWidget final isLarge = isLargeScreenSize(context); final isMedium = isMediumScreenSize(context); return BlocProvider( - create: (context) => WallSensorBloc(deviceId: device.uuid!) - ..add(WallSensorFetchStatusEvent()), + create: (context) => + WallSensorBloc(deviceId: device.uuid!)..add(WallSensorFetchStatusEvent()), child: BlocBuilder( builder: (context, state) { - if (state is WallSensorLoadingInitialState || - state is DeviceReportsLoadingState) { + if (state is WallSensorLoadingInitialState || state is DeviceReportsLoadingState) { return const Center(child: CircularProgressIndicator()); } else if (state is WallSensorUpdateState) { - return _buildGridView(context, state.wallSensorModel, isExtraLarge, - isLarge, isMedium); + return _buildGridView(context, state.wallSensorModel, isExtraLarge, isLarge, isMedium); } else if (state is DeviceReportsState) { return ReportsTable( report: state.deviceReport, - thirdColumnTitle: - state.code == 'illuminance_value' ? "Value" : 'Status', - thirdColumnDescription: - state.code == 'illuminance_value' ? "Lux" : null, + thirdColumnTitle: state.code == 'illuminance_value' ? "Value" : 'Status', + thirdColumnDescription: state.code == 'illuminance_value' ? "Lux" : null, onRowTap: (index) {}, onClose: () { context.read().add(BackToGridViewEvent()); @@ -57,8 +52,7 @@ class WallSensorControlsView extends StatelessWidget ); } else if (state is DeviceReportsFailedState) { final model = context.read().deviceStatus; - return _buildGridView( - context, model, isExtraLarge, isLarge, isMedium); + return _buildGridView(context, model, isExtraLarge, isLarge, isMedium); } return const Center(child: Text('Error fetching status')); }, @@ -66,8 +60,8 @@ class WallSensorControlsView extends StatelessWidget ); } - Widget _buildGridView(BuildContext context, WallSensorModel model, - bool isExtraLarge, bool isLarge, bool isMedium) { + Widget _buildGridView( + BuildContext context, WallSensorModel model, bool isExtraLarge, bool isLarge, bool isMedium) { return GridView( padding: const EdgeInsets.symmetric(horizontal: 50), shrinkWrap: true, @@ -136,11 +130,10 @@ class WallSensorControlsView extends StatelessWidget maxValue: 10000, steps: 1, description: 'sec', - action: (int value) => - context.read().add(WallSensorChangeValueEvent( - code: 'no_one_time', - value: value, - ))), + action: (int value) => context.read().add(WallSensorChangeValueEvent( + code: 'no_one_time', + value: value, + ))), PresenceUpdateData( value: model.farDetection.toDouble(), title: 'Far Detection:', @@ -157,8 +150,9 @@ class WallSensorControlsView extends StatelessWidget ), GestureDetector( onTap: () { - context.read().add(GetDeviceReportsEvent( - code: 'illuminance_value', deviceUuid: device.uuid!)); + context + .read() + .add(GetDeviceReportsEvent(code: 'illuminance_value', deviceUuid: device.uuid!)); }, child: const PresenceStaticWidget( icon: Assets.illuminanceRecordIcon, @@ -167,8 +161,9 @@ class WallSensorControlsView extends StatelessWidget ), GestureDetector( onTap: () { - context.read().add(GetDeviceReportsEvent( - code: 'presence_state', deviceUuid: device.uuid!)); + context + .read() + .add(GetDeviceReportsEvent(code: 'presence_state', deviceUuid: device.uuid!)); }, child: const PresenceStaticWidget( icon: Assets.presenceRecordIcon, diff --git a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart index 5b9ab5b7..aeb53260 100644 --- a/lib/pages/visitor_password/bloc/visitor_password_bloc.dart +++ b/lib/pages/visitor_password/bloc/visitor_password_bloc.dart @@ -76,7 +76,8 @@ class VisitorPasswordBloc extends Bloc selectTimeVisitorPassword( SelectTimeVisitorPassword event, - Emitter emit,) async { + Emitter emit, + ) async { final DateTime? picked = await showDatePicker( context: event.context, initialDate: DateTime.now(), @@ -359,7 +360,6 @@ class VisitorPasswordBloc extends Bloc jsonData = json; List passwordList = jsonData.map((jsonItem) { return PasswordModel.fromJson(jsonItem);