diff --git a/assets/icons/frequency_icon.svg b/assets/icons/frequency_icon.svg new file mode 100644 index 00000000..d093af37 --- /dev/null +++ b/assets/icons/frequency_icon.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/power_active_icon.svg b/assets/icons/power_active_icon.svg new file mode 100644 index 00000000..28b1412a --- /dev/null +++ b/assets/icons/power_active_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/speedo_meter.svg b/assets/icons/speedo_meter.svg new file mode 100644 index 00000000..be3b5c4b --- /dev/null +++ b/assets/icons/speedo_meter.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/volt-meter.svg b/assets/icons/volt-meter.svg new file mode 100644 index 00000000..6691a7dd --- /dev/null +++ b/assets/icons/volt-meter.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/volt_meter_icon.svg b/assets/icons/volt_meter_icon.svg new file mode 100644 index 00000000..97b9037d --- /dev/null +++ b/assets/icons/volt_meter_icon.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/voltage_icon.svg b/assets/icons/voltage_icon.svg new file mode 100644 index 00000000..29b06678 --- /dev/null +++ b/assets/icons/voltage_icon.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 d4b5b21a..8edb0a1e 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 @@ -18,6 +18,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/power_clamp/view/power_clamp_batch_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/view/smart_power_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'; @@ -94,6 +96,10 @@ mixin RouteControlsBasedCode { return WaterLeakView( deviceId: device.uuid!, ); + case 'PC': + return SmartPowerDeviceControl( + deviceId: device.uuid!, + ); default: return const SizedBox(); } @@ -224,6 +230,13 @@ mixin RouteControlsBasedCode { .map((e) => e.uuid!) .toList(), ); + case 'PC': + return PowerClampBatchControlView( + deviceIds: devices + .where((e) => (e.productType == 'PC')) + .map((e) => e.uuid!) + .toList(), + ); default: return const SizedBox(); } diff --git a/lib/pages/device_managment/power_clamp/bloc/smart_power_bloc.dart b/lib/pages/device_managment/power_clamp/bloc/smart_power_bloc.dart new file mode 100644 index 00000000..04cb1b54 --- /dev/null +++ b/lib/pages/device_managment/power_clamp/bloc/smart_power_bloc.dart @@ -0,0 +1,784 @@ +import 'dart:async'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_event.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_state.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/models/device_event.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_chart.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +class SmartPowerBloc extends Bloc { + SmartPowerBloc({required this.deviceId}) : super(SmartPowerInitial()) { + on(_onFetchDeviceStatus); + on(_onArrowPressed); + on(_onFetchBatchStatus); + on(_onPageChanged); + on(_onBatchControl); + on(_filterRecordsByDate); + on(checkDayMonthYearSelected); + on(_onFactoryReset); + } + + late PowerClampModel deviceStatus; + late PowerClampBatchModel deviceBatchStatus; + final String deviceId; + Timer? _timer; + List> phaseData = []; + int currentPage = 0; + + List record = [ + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2024-10-23 11:15:43'), + value: '2286'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2024-10-23 11:15:35'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2024-10-23 11:15:29'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2024-10-23 11:15:25'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2024-10-23 11:15:21'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2024-10-23 11:15:17'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2024-10-23 11:15:07'), + value: '2286'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2024-10-23 11:14:47'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2024-10-23 11:14:40'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2024-10-23 11:14:23'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2024-10-23 11:14:13'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-10-23 11:15:43'), + value: '2286'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-10-23 11:15:35'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-10-23 11:15:29'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-10-23 11:15:25'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-10-23 11:15:21'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-10-23 11:15:17'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-10-23 11:15:07'), + value: '2286'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-10-23 11:14:47'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-10-23 11:14:40'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-10-23 11:14:23'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-10-23 11:14:13'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-23 11:15:43'), + value: '2286'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-23 11:15:35'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-23 11:15:29'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-23 11:15:25'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-23 11:15:21'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-23 11:15:17'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-23 11:15:07'), + value: '2286'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-23 11:14:47'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-23 11:14:40'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-23 11:14:23'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-23 11:14:13'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-11 11:15:43'), + value: '2286'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-11 11:15:35'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-12 11:15:29'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-13 11:15:25'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-14 11:15:21'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-15 11:15:17'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-16 11:15:07'), + value: '2286'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-17 11:14:47'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-18 11:14:40'), + value: '2284'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-19 11:14:23'), + value: '2285'), + EventDevice( + code: 'VoltageA', + eventTime: DateTime.parse('2023-02-20 11:14:13'), + value: '2284'), + ]; + + FutureOr _onFetchDeviceStatus( + SmartPowerFetchDeviceEvent event, Emitter emit) async { + emit(SmartPowerLoading()); + try { + var status = + await DevicesManagementApi().getPowerClampInfo(event.deviceId); + deviceStatus = PowerClampModel.fromJson(status); + + phaseData = [ + { + 'name': 'Phase A', + 'voltage': '${deviceStatus.status.phaseA.dataPoints[0].value / 10} V', + 'current': '${deviceStatus.status.phaseA.dataPoints[1].value / 10} A', + 'activePower': '${deviceStatus.status.phaseA.dataPoints[2].value} W', + 'powerFactor': '${deviceStatus.status.phaseA.dataPoints[3].value}', + }, + { + 'name': 'Phase B', + 'voltage': '${deviceStatus.status.phaseB.dataPoints[0].value / 10} V', + 'current': '${deviceStatus.status.phaseB.dataPoints[1].value / 10} A', + 'activePower': '${deviceStatus.status.phaseB.dataPoints[2].value} W', + 'powerFactor': '${deviceStatus.status.phaseB.dataPoints[3].value}', + }, + { + 'name': 'Phase C', + 'voltage': '${deviceStatus.status.phaseC.dataPoints[0].value / 10} V', + 'current': '${deviceStatus.status.phaseC.dataPoints[1].value / 10} A', + 'activePower': '${deviceStatus.status.phaseC.dataPoints[2].value} W', + 'powerFactor': '${deviceStatus.status.phaseC.dataPoints[3].value}', + }, + ]; + emit(GetDeviceStatus()); + } catch (e) { + emit(SmartPowerError(e.toString())); + } + } + + FutureOr _onArrowPressed( + SmartPowerArrowPressedEvent event, Emitter emit) { + currentPage = (currentPage + event.direction + 4) % 4; + emit(SmartPowerStatusLoaded(deviceStatus, currentPage)); + emit(GetDeviceStatus()); + } + + FutureOr _onPageChanged( + SmartPowerPageChangedEvent event, Emitter emit) { + currentPage = event.page; + emit(SmartPowerStatusLoaded(deviceStatus, currentPage)); + emit(GetDeviceStatus()); + } + + Future _onFactoryReset( + SmartPowerFactoryReset event, Emitter emit) async { + emit(SmartPowerLoading()); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryReset, + event.deviceId, + ); + if (response) { + emit(SmartPowerInitial()); + } else { + emit(SmartPowerError('Factory reset failed')); + } + } catch (e) { + emit(SmartPowerError(e.toString())); + } + } + + Future _onBatchControl( + PowerBatchControlEvent event, Emitter emit) async { + final oldValue = deviceStatus.status; + + _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( + SmartPowerFetchBatchEvent event, Emitter emit) async { + emit(SmartPowerLoading()); + try { + final response = + await DevicesManagementApi().getPowerStatus(event.devicesIds); + PowerClampBatchModel deviceStatus = + PowerClampBatchModel.fromJson(response); + + emit(SmartPowerLoadBatchControll(deviceStatus)); + } catch (e) { + debugPrint('=========error====$e'); + emit(SmartPowerError(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(statusPower: value); + } else if (code == 'battery_percentage') { + deviceStatus = deviceStatus.copyWith(statusPower: value); + } + } + + void _revertValueAndEmit(String deviceId, String code, dynamic oldValue, + Emitter emit) { + _updateLocalValue(code, oldValue); + emit(SmartPowerLoadBatchControll(deviceBatchStatus)); + } + + @override + Future close() { + _timer?.cancel(); + return super.close(); + } + + List filteredRecords = []; + + int currentIndex = 0; + final List views = ['Day', 'Month', 'Year']; + + Widget dateSwitcher() { + void switchView(int direction) { + currentIndex = (currentIndex + direction + views.length) % views.length; + } + + return StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(Icons.arrow_left), + onPressed: () { + setState(() { + switchView(-1); + }); + }, + ), + Text( + views[currentIndex], + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), + ), + IconButton( + icon: const Icon(Icons.arrow_right), + onPressed: () { + setState(() { + switchView(1); + }); + }, + ), + ], + ); + }, + ); + } + + Future selectMonthAndYear(BuildContext context) async { + int selectedYear = DateTime.now().year; + int selectedMonth = DateTime.now().month; + + FixedExtentScrollController yearController = + FixedExtentScrollController(initialItem: selectedYear - 1905); + FixedExtentScrollController monthController = + FixedExtentScrollController(initialItem: selectedMonth - 1); + + return await showDialog( + context: context, + builder: (BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + color: Colors.white, + height: 350, + width: 350, + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(16.0), + child: Text( + 'Select Month and Year', + style: + TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ), + const Divider(), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + Expanded( + child: ListWheelScrollView.useDelegate( + controller: yearController, + overAndUnderCenterOpacity: 0.2, + itemExtent: 50, + onSelectedItemChanged: (index) { + selectedYear = 1905 + index; + }, + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) { + return Center( + child: Text( + (1905 + index).toString(), + style: const TextStyle(fontSize: 18), + ), + ); + }, + childCount: 200, + ), + ), + ), + Expanded( + flex: 2, + child: ListWheelScrollView.useDelegate( + controller: monthController, + overAndUnderCenterOpacity: 0.2, + itemExtent: 50, + onSelectedItemChanged: (index) { + selectedMonth = index + 1; + }, + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) { + return Center( + child: Text( + DateFormat.MMMM() + .format(DateTime(0, index + 1)), + style: const TextStyle(fontSize: 18), + ), + ); + }, + childCount: 12, + ), + ), + ), + const Spacer(), + ], + ), + ), + const Divider(), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + child: const Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + final selectedDateTime = + DateTime(selectedYear, selectedMonth); + Navigator.of(context).pop(selectedDateTime); + }, + ), + ], + ), + ), + ], + ), + ), + ], + ); + }, + ); + } + + Future selectYear(BuildContext context) async { + int selectedYear = DateTime.now().year; + FixedExtentScrollController yearController = + FixedExtentScrollController(initialItem: selectedYear - 1905); + + return await showDialog( + context: context, + builder: (BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + color: Colors.white, + height: 350, + width: 350, + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(16.0), + child: Text( + 'Select Year', + style: + TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ), + const Divider(), + Expanded( + child: ListWheelScrollView.useDelegate( + controller: yearController, + overAndUnderCenterOpacity: 0.2, + itemExtent: 50, + onSelectedItemChanged: (index) { + selectedYear = 1905 + index; + }, + childDelegate: ListWheelChildBuilderDelegate( + builder: (context, index) { + return Center( + child: Text( + (1905 + index).toString(), + style: const TextStyle(fontSize: 18), + ), + ); + }, + childCount: 200, + ), + ), + ), + const Divider(), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + child: const Text('Cancel'), + onPressed: () { + Navigator.of(context) .pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + final selectedDateTime = DateTime(selectedYear); + Navigator.of(context).pop(selectedDateTime); + }, + ), + ], + ), + ), + ], + ), + ), + ], + ); + }, + ); + } + + Future dayMonthYearPicker({ + required BuildContext context, + }) async { + DateTime selectedDate = DateTime.now(); + + return await showDialog( + context: context, + builder: (BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + color: Colors.white, + height: 350, + width: 350, + child: Column( + children: [ + Expanded( + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.date, + initialDateTime: DateTime.now(), + minimumYear: 1900, + maximumYear: DateTime.now().year, + onDateTimeChanged: (DateTime newDateTime) { + selectedDate = newDateTime; + }, + ), + ), + const Divider(), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + child: const Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context).pop(selectedDate); + }, + ), + ], + ), + ), + ], + ), + ), + ], + ); + }, + ); + } + + DateTime? dateTime = DateTime.now(); + + String formattedDate = DateFormat('yyyy/MM/dd').format(DateTime.now()); + + void checkDayMonthYearSelected( + SelectDateEvent event, Emitter emit) async { + Future Function(BuildContext context)? dateSelector; + String dateFormat; + switch (currentIndex) { + case 0: + dateSelector = (context) { + return dayMonthYearPicker(context: context); + }; + dateFormat = 'yyyy/MM/dd'; + break; + case 1: + dateSelector = (context) { + return selectMonthAndYear(context); + }; + dateFormat = 'yyyy-MM'; + break; + case 2: + dateSelector = (context) { + return selectYear(context); + }; + dateFormat = 'yyyy'; + break; + default: + return; + } + Future.delayed(const Duration(milliseconds: 500), () { + emit(SmartPowerLoading()); + }); + // Use the selected picker + await dateSelector(event.context).then((newDate) { + if (newDate.toString() == 'null') { + emit(GetDeviceStatus()); + } else { + dateTime = newDate; + add(FilterRecordsByDateEvent( + selectedDate: newDate!, + viewType: views[currentIndex], + )); + } + // formattedDate = newDate.toString(); + }); + emit(FilterRecordsState(filteredRecords: energyDataList)); + + } + + List energyDataList = []; + void _filterRecordsByDate( + FilterRecordsByDateEvent event, Emitter emit) { + emit(SmartPowerLoading()); + + if (event.viewType == 'Year') { + formattedDate = event.selectedDate.year.toString(); + filteredRecords = record + .where((record) => record.eventTime!.year == event.selectedDate.year) + .toList(); + } else if (event.viewType == 'Month') { + formattedDate = + "${event.selectedDate.year.toString()}-${getMonthShortName(event.selectedDate.month)}"; + + filteredRecords = record + .where((record) => + record.eventTime!.year == event.selectedDate.year && + record.eventTime!.month == event.selectedDate.month) + .toList(); + } else if (event.viewType == 'Day') { + formattedDate = + "${event.selectedDate.year.toString()}-${getMonthShortName(event.selectedDate.month)}-${event.selectedDate.day}"; + + filteredRecords = record + .where((record) => + record.eventTime!.year == event.selectedDate.year && + record.eventTime!.month == event.selectedDate.month && + record.eventTime!.day == event.selectedDate.day) + .toList(); + } + + selectDateRange(); + energyDataList = filteredRecords.map((eventDevice) { + return EnergyData( + event.viewType == 'Year' + ? getMonthShortName( + int.tryParse(DateFormat('MM').format(eventDevice.eventTime!))!) + : event.viewType == 'Month' + ? DateFormat('yyyy/MM/dd').format(eventDevice.eventTime!) + : DateFormat('HH:mm:ss').format(eventDevice.eventTime!), + double.parse(eventDevice.value!), + ); + }).toList(); + emit(FilterRecordsState(filteredRecords: energyDataList)); + } + + String getMonthShortName(int month) { + final date = DateTime(0, month); + return DateFormat.MMM().format(date); + } + + String endChartDate = ''; + + void selectDateRange() async { + DateTime startDate = dateTime!; + DateTime endDate = DateTime(startDate.year, startDate.month + 1, 1) + .subtract(Duration(days: 1)); + String formattedEndDate = DateFormat('dd/MM/yyyy').format(endDate); + endChartDate = ' - $formattedEndDate'; + } +} diff --git a/lib/pages/device_managment/power_clamp/bloc/smart_power_event.dart b/lib/pages/device_managment/power_clamp/bloc/smart_power_event.dart new file mode 100644 index 00000000..1985c67c --- /dev/null +++ b/lib/pages/device_managment/power_clamp/bloc/smart_power_event.dart @@ -0,0 +1,115 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; + +class SmartPowerEvent extends Equatable { + @override + List get props => []; +} + +class SmartPowerFetchDeviceEvent extends SmartPowerEvent { + final String deviceId; + + SmartPowerFetchDeviceEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class SmartPowerControl extends SmartPowerEvent { + final String deviceId; + final String code; + final bool value; + + SmartPowerControl( + {required this.deviceId, required this.code, required this.value}); + + @override + List get props => [deviceId, code, value]; +} + +class SmartPowerFetchBatchEvent extends SmartPowerEvent { + final List devicesIds; + + SmartPowerFetchBatchEvent(this.devicesIds); + + @override + List get props => [devicesIds]; +} + +class SmartPowerBatchControl extends SmartPowerEvent { + final List devicesIds; + final String code; + final bool value; + + SmartPowerBatchControl( + {required this.devicesIds, required this.code, required this.value}); + + @override + List get props => [devicesIds, code, value]; +} + +class SmartPowerFactoryReset extends SmartPowerEvent { + final String deviceId; + final FactoryResetModel factoryReset; + + SmartPowerFactoryReset({required this.deviceId, required this.factoryReset}); + + @override + List get props => [deviceId, factoryReset]; +} + +class PageChangedEvent extends SmartPowerEvent { + final int newPage; + PageChangedEvent(this.newPage); +} + +class PageArrowPressedEvent extends SmartPowerEvent { + final int direction; + PageArrowPressedEvent(this.direction); +} + +class SmartPowerArrowPressedEvent extends SmartPowerEvent { + final int direction; + SmartPowerArrowPressedEvent(this.direction); +} + +class SmartPowerPageChangedEvent extends SmartPowerEvent { + final int page; + SmartPowerPageChangedEvent(this.page); +} + +class SelectDateEvent extends SmartPowerEvent { + BuildContext context; + SelectDateEvent({required this.context}); +} + +class FilterRecordsByDateEvent extends SmartPowerEvent { + final DateTime selectedDate; + final String viewType; // 'Day', 'Month', 'Year' + + FilterRecordsByDateEvent( + {required this.selectedDate, required this.viewType}); +} + +class FetchPowerClampBatchStatusEvent extends SmartPowerEvent { + final List deviceIds; + + FetchPowerClampBatchStatusEvent(this.deviceIds); + + @override + List get props => [deviceIds]; +}class PowerBatchControlEvent extends SmartPowerEvent { + final List deviceIds; + final String code; + final dynamic value; + + PowerBatchControlEvent({ + required this.deviceIds, + required this.code, + required this.value, + }); + + @override + List get props => [deviceIds, code, value]; +} \ No newline at end of file diff --git a/lib/pages/device_managment/power_clamp/bloc/smart_power_state.dart b/lib/pages/device_managment/power_clamp/bloc/smart_power_state.dart new file mode 100644 index 00000000..62af9e78 --- /dev/null +++ b/lib/pages/device_managment/power_clamp/bloc/smart_power_state.dart @@ -0,0 +1,74 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_chart.dart'; + +class SmartPowerState extends Equatable { + @override + List get props => []; +} + +class SmartPowerInitial extends SmartPowerState {} + +class SmartPowerLoading extends SmartPowerState {} +class GetDeviceStatus extends SmartPowerState {} +//GetDeviceStatus + +class SmartPowerLoadBatchControll extends SmartPowerState { + final PowerClampBatchModel status; + + SmartPowerLoadBatchControll(this.status); + + @override + List get props => [status]; +} + +class DateSelectedState extends SmartPowerState {} + +class SmartPowerStatusLoaded extends SmartPowerState { + final PowerClampModel deviceStatus; + final int currentPage; + SmartPowerStatusLoaded(this.deviceStatus, this.currentPage); +} + +class SmartPowerError extends SmartPowerState { + final String message; + + SmartPowerError(this.message); + + @override + List get props => [message]; +} + +class SmartPowerControlError extends SmartPowerState { + final String message; + + SmartPowerControlError(this.message); + + @override + List get props => [message]; +} + +class SmartPowerBatchControlError extends SmartPowerState { + final String message; + + SmartPowerBatchControlError(this.message); + + @override + List get props => [message]; +} + +class SmartPowerBatchStatusLoaded extends SmartPowerState { + final List status; + + SmartPowerBatchStatusLoaded(this.status); + + @override + List get props => [status]; +} + +class FilterRecordsState extends SmartPowerState { + final List filteredRecords; + + FilterRecordsState({required this.filteredRecords}); +} diff --git a/lib/pages/device_managment/power_clamp/models/device_event.dart b/lib/pages/device_managment/power_clamp/models/device_event.dart new file mode 100644 index 00000000..09f7b46e --- /dev/null +++ b/lib/pages/device_managment/power_clamp/models/device_event.dart @@ -0,0 +1,23 @@ + +class EventDevice { + final String? code; + final DateTime? eventTime; + final String? value; + + EventDevice({ + this.code, + this.eventTime, + this.value, + }); + + EventDevice.fromJson(Map json) + : code = json['code'] as String?, + eventTime = json['eventTime'] , + value = json['value'] as String?; + + Map toJson() => { + 'code': code, + 'eventTime': eventTime, + 'value': value, + }; +} diff --git a/lib/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart b/lib/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart new file mode 100644 index 00000000..1812d1c9 --- /dev/null +++ b/lib/pages/device_managment/power_clamp/models/power_clamp_batch_model.dart @@ -0,0 +1,49 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +abstract class PowerClampModel1 { + String get productUuid; + String get productType; +} + +class PowerClampBatchModel extends PowerClampModel1 { + @override + final String productUuid; + @override + final String productType; + final List status; + + PowerClampBatchModel({ + required this.productUuid, + required this.productType, + required this.status, + }); + + factory PowerClampBatchModel.fromJson(Map json) { + String productUuid = json['productUuid'] ?? ''; + String productType = json['productType'] ?? ''; + + List statusList = []; + if (json['status'] != null && json['status'] is List) { + statusList = + (json['status'] as List).map((e) => Status.fromJson(e)).toList(); + } + + return PowerClampBatchModel( + productUuid: productUuid, + productType: productType, + status: statusList, + ); + } + + PowerClampBatchModel copyWith({ + String? productUuid, + String? productType, + List? status, + }) { + return PowerClampBatchModel( + productUuid: productUuid ?? this.productUuid, + productType: productType ?? this.productType, + status: status ?? this.status, + ); + } +} diff --git a/lib/pages/device_managment/power_clamp/models/power_clamp_model.dart b/lib/pages/device_managment/power_clamp/models/power_clamp_model.dart new file mode 100644 index 00000000..914a255b --- /dev/null +++ b/lib/pages/device_managment/power_clamp/models/power_clamp_model.dart @@ -0,0 +1,98 @@ +// PowerClampModel class to represent the response +class PowerClampModel { + String productUuid; + String productType; + PowerStatus status; + + PowerClampModel({ + required this.productUuid, + required this.productType, + required this.status, + }); + + factory PowerClampModel.fromJson(Map json) { + return PowerClampModel( + productUuid: json['productUuid'], + productType: json['productType'], + status: PowerStatus.fromJson(json['status']), + ); + } + + PowerClampModel copyWith({ + String? productUuid, + String? productType, + PowerStatus? statusPower, + }) { + return PowerClampModel( + productUuid: productUuid ?? this.productUuid, + productType: productType ?? this.productType, + status: statusPower ?? this.status, + ); + } +} + +class PowerStatus { + Phase phaseA; + Phase phaseB; + Phase phaseC; + Phase general; + + PowerStatus({ + required this.phaseA, + required this.phaseB, + required this.phaseC, + required this.general, + }); + + factory PowerStatus.fromJson(Map json) { + return PowerStatus( + phaseA: Phase.fromJson(json['phaseA']), + phaseB: Phase.fromJson(json['phaseB']), + phaseC: Phase.fromJson(json['phaseC']), + general: Phase.fromJson(json['general'] + // List.from( + // json['general'].map((x) => DataPoint.fromJson(x))), + )); + } +} + +class Phase { + List dataPoints; + + Phase({required this.dataPoints}); + + factory Phase.fromJson(List json) { + return Phase( + dataPoints: json.map((x) => DataPoint.fromJson(x)).toList(), + ); + } +} + +class DataPoint { + dynamic code; + dynamic customName; + dynamic dpId; + dynamic time; + dynamic type; + dynamic value; + + DataPoint({ + required this.code, + required this.customName, + required this.dpId, + required this.time, + required this.type, + required this.value, + }); + + factory DataPoint.fromJson(Map json) { + return DataPoint( + code: json['code'], + customName: json['customName'], + dpId: json['dpId'], + time: json['time'], + type: json['type'], + value: json['value'], + ); + } +} diff --git a/lib/pages/device_managment/power_clamp/view/phase_widget.dart b/lib/pages/device_managment/power_clamp/view/phase_widget.dart new file mode 100644 index 00000000..223acd95 --- /dev/null +++ b/lib/pages/device_managment/power_clamp/view/phase_widget.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_info_card.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class PhaseWidget extends StatefulWidget { + final List> phaseData; + + PhaseWidget({ + required this.phaseData, + }); + @override + _PhaseWidgetState createState() => _PhaseWidgetState(); +} + +class _PhaseWidgetState extends State { + int _selectedPhaseIndex = 0; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SizedBox(height: 10), + Row( + children: List.generate(widget.phaseData.length, (index) { + return InkWell( + onTap: () { + setState(() { + _selectedPhaseIndex = index; + }); + }, + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 10), + child: Text( + widget.phaseData[index]['name'], + style: TextStyle( + fontWeight: FontWeight.bold, + color: _selectedPhaseIndex == index + ? Colors.black + : Colors.grey, + ), + ), + ), + ); + }), + ), + SizedBox(height: 10), + _selectedPhaseIndex == 0 + ? phase( + totalActive: widget.phaseData[0]['activePower'] ?? '0', + totalCurrent: widget.phaseData[0]['current'] ?? '0', + totalFactor: widget.phaseData[0]['powerFactor'] ?? '0', + totalVoltage: widget.phaseData[0]['voltage'] ?? '0', + ) + : _selectedPhaseIndex == 1 + ? phase( + totalActive: widget.phaseData[1]['activePower'] ?? '0', + totalCurrent: widget.phaseData[1]['current'] ?? '0', + totalFactor: widget.phaseData[1]['powerFactor'] ?? '0', + totalVoltage: widget.phaseData[1]['voltage'] ?? '0', + ) + : phase( + totalActive: widget.phaseData[2]['activePower'] ?? '0', + totalCurrent: widget.phaseData[2]['current'] ?? '0', + totalFactor: widget.phaseData[2]['powerFactor'] ?? '0', + totalVoltage: widget.phaseData[2]['voltage'] ?? '0', + ), + ], + ); + } +} + +class phase extends StatelessWidget { + const phase({ + super.key, + required this.totalVoltage, + required this.totalCurrent, + required this.totalActive, + required this.totalFactor, + }); + + final String totalVoltage; + final String totalCurrent; + final String totalActive; + final String totalFactor; + + @override + Widget build(BuildContext context) { + return Container( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + PowerClampInfoCard( + iconPath: Assets.voltageIcon, + title: 'Voltage', + value: totalVoltage, + unit: '', + ), + PowerClampInfoCard( + iconPath: Assets.voltMeterIcon, + title: 'Current', + value: totalCurrent, + unit: '', + ), + PowerClampInfoCard( + iconPath: Assets.powerActiveIcon, + title: 'Active Power', + value: totalActive, + unit: '', + ), + PowerClampInfoCard( + iconPath: Assets.speedoMeter, + title: 'Power Factor', + value: totalFactor, + unit: '', + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/power_clamp/view/power_chart.dart b/lib/pages/device_managment/power_clamp/view/power_chart.dart new file mode 100644 index 00000000..273537ba --- /dev/null +++ b/lib/pages/device_managment/power_clamp/view/power_chart.dart @@ -0,0 +1,281 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class EnergyConsumptionPage extends StatefulWidget { + final List chartData; + final double totalConsumption; + final String date; + final String formattedDate; + final Widget widget; + final Function()? onTap; + + EnergyConsumptionPage({ + required this.chartData, + required this.totalConsumption, + required this.date, + required this.widget, + required this.onTap, + required this.formattedDate, + }); + + @override + _EnergyConsumptionPageState createState() => _EnergyConsumptionPageState(); +} + +class _EnergyConsumptionPageState extends State { + late List _chartData; + + @override + void initState() { + _chartData = widget.chartData; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Container( + color: ColorsManager.whiteColors, + child: Column( + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Consumption', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 20, + ), + ), + Text( + '8623.20 kWh', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 20, + ), + ), + ], + ), + const Row( + children: [ + Text( + 'Energy consumption', + style: TextStyle( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w700, + fontSize: 12, + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.formattedDate, + style: const TextStyle( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400, + fontSize: 8, + ), + ), + const Text( + '1000.00 kWh', + style: TextStyle( + color: ColorsManager.grayColor, + fontWeight: FontWeight.w400, + fontSize: 8, + ), + ), + ], + ), + Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 15), + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.12, + child: LineChart( + LineChartData( + lineTouchData: LineTouchData( + handleBuiltInTouches: true, + touchSpotThreshold: 2, + getTouchLineEnd: (barData, spotIndex) { + return 10.0; + }, + touchTooltipData: LineTouchTooltipData( + getTooltipColor: (touchTooltipItem) => Colors.white, + tooltipRoundedRadius: 10.0, + tooltipPadding: const EdgeInsets.all(8.0), + tooltipBorder: const BorderSide( + color: ColorsManager.grayColor, width: 1), + getTooltipItems: (List touchedSpots) { + return touchedSpots.map((spot) { + return LineTooltipItem( + '${spot.x},\n ${spot.y.toStringAsFixed(2)} kWh', + const TextStyle( + color: Colors.blue, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ); + }).toList(); + }, + )), + titlesData: FlTitlesData( + bottomTitles: const AxisTitles( + sideTitles: SideTitles( + showTitles: false, + ), + ), + leftTitles: const AxisTitles( + sideTitles: SideTitles( + showTitles: false, + ), + ), + rightTitles: const AxisTitles( + sideTitles: SideTitles( + showTitles: false, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + reservedSize: 70, + getTitlesWidget: (value, meta) { + int index = value.toInt(); + if (index >= 0 && index < _chartData.length) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: RotatedBox( + quarterTurns: -1, + child: Text(_chartData[index].time, + style: TextStyle(fontSize: 10)), + ), + ); + } + return const SizedBox.shrink(); + }, + ), + ), + ), + gridData: FlGridData( + show: true, + drawVerticalLine: true, + horizontalInterval: 1, + verticalInterval: 1, + getDrawingVerticalLine: (value) { + return FlLine( + color: Colors.grey.withOpacity(0.2), + dashArray: [8, 8], + strokeWidth: 1, + ); + }, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Colors.grey.withOpacity(0.2), + dashArray: [5, 5], + strokeWidth: 1, + ); + }, + drawHorizontalLine: false, + ), + lineBarsData: [ + LineChartBarData( + preventCurveOvershootingThreshold: 0.1, + curveSmoothness: 0.5, + preventCurveOverShooting: true, + aboveBarData: BarAreaData(), + spots: _chartData + .asMap() + .entries + .map((entry) => FlSpot(entry.key.toDouble(), + entry.value.consumption)) + .toList(), + isCurved: true, + color: ColorsManager.primaryColor.withOpacity(0.6), + show: true, + shadow: const Shadow(color: Colors.black12), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + ColorsManager.primaryColor.withOpacity(0.5), + Colors.blue.withOpacity(0.1), + ], + begin: Alignment.center, + end: Alignment.bottomCenter, + ), + ), + dotData: const FlDotData( + show: false, + ), + isStrokeCapRound: true, + barWidth: 2, + ), + ], + borderData: FlBorderData( + show: false, + border: Border.all( + color: Color(0xff023DFE).withOpacity(0.7), + width: 10, + ), + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + color: ColorsManager.graysColor, + borderRadius: BorderRadius.circular(10), + ), + child: Container(child: widget.widget), + ), + ), + SizedBox( + width: 20, + ), + Expanded( + child: Container( + padding: const EdgeInsets.all(5.0), + decoration: BoxDecoration( + color: ColorsManager.graysColor, + borderRadius: BorderRadius.circular(10), + ), + child: InkWell( + onTap: widget.onTap, + child: Center( + child: SizedBox( + child: Padding( + padding: const EdgeInsets.all(5), + child: Text(widget.date), + ), + ), + ), + ), + ), + ), + ], + ), + ) + ], + ), + ], + ), + ); + } +} + +class EnergyData { + EnergyData(this.time, this.consumption); + final String time; + final double consumption; +} diff --git a/lib/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart b/lib/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart new file mode 100644 index 00000000..c0244845 --- /dev/null +++ b/lib/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart @@ -0,0 +1,66 @@ +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/power_clamp/bloc/smart_power_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_event.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_state.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_batch_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/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class PowerClampBatchControlView extends StatelessWidget + with HelperResponsiveLayout { + final List deviceIds; + + const PowerClampBatchControlView({Key? key, required this.deviceIds}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => SmartPowerBloc(deviceId: deviceIds.first) + ..add(SmartPowerFetchBatchEvent(deviceIds)), + child: BlocBuilder( + builder: (context, state) { + if (state is SmartPowerLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is SmartPowerLoadBatchControll) { + return _buildStatusControls(context, state.status); + } else if (state is SmartPowerError) { + return Center(child: Text('Error: ${state.message}')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ); + } + + Widget _buildStatusControls( + BuildContext context, PowerClampBatchModel 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(SmartPowerFactoryReset( + deviceId: deviceIds.first, + factoryReset: FactoryResetModel(devicesUuid: deviceIds))); + }, + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/power_clamp/view/power_info_card.dart b/lib/pages/device_managment/power_clamp/view/power_info_card.dart new file mode 100644 index 00000000..b4dd487a --- /dev/null +++ b/lib/pages/device_managment/power_clamp/view/power_info_card.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class PowerClampInfoCard extends StatelessWidget { + final String iconPath; + final String title; + final String value; + final String unit; + + const PowerClampInfoCard({ + Key? key, + required this.iconPath, + required this.title, + required this.value, + required this.unit, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Expanded( + child: Padding( + padding: const EdgeInsets.all(5.0), + child: Container( + decoration: BoxDecoration( + color: ColorsManager.graysColor, + borderRadius: BorderRadius.circular(20), + ), + height: 55, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + SvgPicture.asset( + iconPath, + fit: BoxFit.fill, + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + title, + style: TextStyle( + fontSize: 8, + fontWeight: FontWeight.w400, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + value, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w700, + ), + ), + Text( + unit, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ], + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/power_clamp/view/smart_power_device_control.dart b/lib/pages/device_managment/power_clamp/view/smart_power_device_control.dart new file mode 100644 index 00000000..635f44ea --- /dev/null +++ b/lib/pages/device_managment/power_clamp/view/smart_power_device_control.dart @@ -0,0 +1,268 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_event.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/bloc/smart_power_state.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_chart.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_info_card.dart'; +import 'package:syncrow_web/pages/device_managment/power_clamp/view/phase_widget.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/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +//Smart Power Clamp +class SmartPowerDeviceControl extends StatelessWidget + with HelperResponsiveLayout { + final String deviceId; + + const SmartPowerDeviceControl({super.key, required this.deviceId}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => SmartPowerBloc(deviceId: deviceId) + ..add(SmartPowerFetchDeviceEvent(deviceId)), + child: BlocBuilder( + builder: (context, state) { + final _blocProvider = BlocProvider.of(context); + + if (state is SmartPowerLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is GetDeviceStatus) { + return _buildStatusControls( + currentPage: _blocProvider.currentPage, + context: context, + blocProvider: _blocProvider, + ); + } else if (state is FilterRecordsState) { + return _buildStatusControls( + currentPage: _blocProvider.currentPage, + context: context, + blocProvider: _blocProvider, + ); + } + return const Center(child: CircularProgressIndicator()); + // } + }, + ), + ); + } + + Widget _buildStatusControls({ + required BuildContext context, + required SmartPowerBloc blocProvider, + required int currentPage, + }) { + PageController _pageController = PageController(initialPage: currentPage); + return Container( + child: DeviceControlsContainer( + child: Column( + children: [ + const Row( + children: [ + Text( + 'Live', + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.w700, + color: ColorsManager.grayColor), + ), + ], + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + PowerClampInfoCard( + iconPath: Assets.powerActiveIcon, + title: 'Active', + value: blocProvider + .deviceStatus.status.general.dataPoints[2].value + .toString(), + unit: '', + ), + PowerClampInfoCard( + iconPath: Assets.voltMeterIcon, + title: 'Current', + value: blocProvider + .deviceStatus.status.general.dataPoints[1].value + .toString(), + unit: ' A', + ), + PowerClampInfoCard( + iconPath: Assets.frequencyIcon, + title: 'Frequency', + value: blocProvider + .deviceStatus.status.general.dataPoints[4].value + .toString(), + unit: ' Hz', + ), + ], + ), + ), + const SizedBox( + height: 10, + ), + PhaseWidget( + phaseData: blocProvider.phaseData, + ), + Container( + padding: const EdgeInsets.only( + top: 10, + left: 20, + right: 20, + ), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(20), + ), + height: 325, + child: Column( + children: [ + Container( + decoration: BoxDecoration( + color: ColorsManager.graysColor, + borderRadius: BorderRadius.circular(20), + ), + height: 50, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(Icons.arrow_left), + onPressed: () { + blocProvider.add(SmartPowerArrowPressedEvent(-1)); + _pageController.previousPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + }, + ), + Text( + currentPage == 0 + ? 'Total' + : currentPage == 1 + ? 'Phase A' + : currentPage == 2 + ? 'Phase B' + : 'Phase C', + style: const TextStyle(fontSize: 18), + ), + IconButton( + icon: const Icon(Icons.arrow_right), + onPressed: () { + blocProvider.add(SmartPowerArrowPressedEvent(1)); + _pageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + }, + ), + ], + ), + ), + const SizedBox( + height: 10, + ), + Expanded( + child: PageView( + controller: _pageController, + onPageChanged: (int page) { + blocProvider.add(SmartPowerPageChangedEvent(page)); + }, + physics: const NeverScrollableScrollPhysics(), + children: [ + EnergyConsumptionPage( + formattedDate: + '${blocProvider.formattedDate}${blocProvider.endChartDate}', + onTap: () { + blocProvider.add(SelectDateEvent(context: context)); + blocProvider.add(FilterRecordsByDateEvent( + selectedDate: blocProvider.dateTime!, + viewType: blocProvider + .views[blocProvider.currentIndex])); + }, + widget: blocProvider.dateSwitcher(), + chartData: blocProvider.energyDataList.isNotEmpty + ? blocProvider.energyDataList + : [ + EnergyData('12:00 AM', 4.0), + EnergyData('01:00 AM', 3.5), + EnergyData('02:00 AM', 3.8), + EnergyData('03:00 AM', 3.2), + EnergyData('04:00 AM', 4.0), + EnergyData('05:00 AM', 3.4), + EnergyData('06:00 AM', 3.2), + EnergyData('07:00 AM', 3.5), + EnergyData('08:00 AM', 3.8), + EnergyData('09:00 AM', 3.6), + EnergyData('10:00 AM', 3.9), + EnergyData('11:00 AM', 4.0), + ], + totalConsumption: 10000, + date: blocProvider.formattedDate, + ), + EnergyConsumptionPage( + formattedDate: blocProvider.formattedDate, + onTap: () { + blocProvider.add(SelectDateEvent(context: context)); + }, + widget: blocProvider.dateSwitcher(), + chartData: blocProvider.energyDataList.isNotEmpty + ? blocProvider.energyDataList + : [ + EnergyData('12:00 AM', 4.0), + EnergyData('01:00 AM', 3.5), + EnergyData('02:00 AM', 3.8), + EnergyData('03:00 AM', 3.2), + EnergyData('04:00 AM', 4.0), + EnergyData('05:00 AM', 3.4), + EnergyData('06:00 AM', 3.2), + EnergyData('07:00 AM', 3.5), + EnergyData('08:00 AM', 3.8), + EnergyData('09:00 AM', 3.6), + EnergyData('10:00 AM', 3.9), + EnergyData('11:00 AM', 4.0), + ], + totalConsumption: 10000, + date: blocProvider.formattedDate, + ), + EnergyConsumptionPage( + formattedDate: blocProvider.formattedDate, + onTap: () { + blocProvider.add(SelectDateEvent(context: context)); + }, + widget: blocProvider.dateSwitcher(), + chartData: blocProvider.energyDataList.isNotEmpty + ? blocProvider.energyDataList + : [ + EnergyData('12:00 AM', 4.0), + EnergyData('01:00 AM', 6.5), + EnergyData('02:00 AM', 3.8), + EnergyData('03:00 AM', 3.2), + EnergyData('04:00 AM', 6.0), + EnergyData('05:00 AM', 3.4), + EnergyData('06:00 AM', 5.2), + EnergyData('07:00 AM', 3.5), + EnergyData('08:00 AM', 3.8), + EnergyData('09:00 AM', 5.6), + EnergyData('10:00 AM', 6.9), + EnergyData('11:00 AM', 6.0), + ], + totalConsumption: 10000, + date: blocProvider.formattedDate, + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} 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 5076ef76..d11b1701 100644 --- a/lib/pages/device_managment/shared/device_batch_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_batch_control_dialog.dart @@ -7,7 +7,8 @@ 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}); diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index bb591b0d..2ee5f9cd 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -50,6 +50,22 @@ class DevicesManagementApi { } } + Future getPowerClampInfo(String deviceId) async { + try { + final response = await HTTPService().get( + path: ApiEndpoints.powerClamp.replaceAll('{powerClampUuid}', deviceId), + showServerMessage: true, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return {}; + } + } + //deviceControl Future deviceControl(String uuid, Status status) async { try { @@ -68,7 +84,8 @@ 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, @@ -92,7 +109,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, @@ -126,7 +144,9 @@ 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); @@ -135,7 +155,8 @@ 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) @@ -174,7 +195,32 @@ class DevicesManagementApi { } } - Future addScheduleRecord(ScheduleEntry sendSchedule, String uuid) async { + getPowerStatus(List uuids) async { + try { + final queryParameters = { + 'devicesUuid': uuids.join(','), + }; + final response = await HTTPService().get( + path: ApiEndpoints.getBatchStatus, + queryParameters: queryParameters, + showServerMessage: true, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + debugPrint('Error fetching $e'); + return DeviceStatus( + productUuid: '', + productType: '', + status: [], + ); + } + } + + Future addScheduleRecord( + ScheduleEntry sendSchedule, String uuid) async { try { final response = await HTTPService().post( path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), @@ -191,10 +237,13 @@ 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 = []; @@ -211,7 +260,10 @@ 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 @@ -232,7 +284,8 @@ class DevicesManagementApi { } } - Future editScheduleRecord(String uuid, ScheduleEntry newSchedule) async { + Future editScheduleRecord( + String uuid, ScheduleEntry newSchedule) async { try { final response = await HTTPService().put( path: ApiEndpoints.scheduleByDeviceId.replaceAll('{deviceUuid}', uuid), diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 23bc5c6c..bf167ab5 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -47,5 +47,7 @@ abstract class ApiEndpoints { static const String updateScheduleByDeviceId = '/schedule/enable/{deviceUuid}'; - static const String factoryReset = '/device/factory/reset/{deviceUuid}'; + static const String factoryReset = '/device/factory/reset/{deviceUuid}'; + static const String powerClamp = + '/device/{powerClampUuid}/power-clamp/status'; } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 14f7a15e..168d8f43 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -60,23 +60,29 @@ class Assets { 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 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 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 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 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"; @@ -169,6 +175,12 @@ class Assets { //assets/icons/2gang.svg static const String twoGang = 'assets/icons/2gang.svg'; + static const String frequencyIcon = "assets/icons/frequency_icon.svg"; + static const String voltMeterIcon = "assets/icons/volt_meter_icon.svg"; + static const String powerActiveIcon = "assets/icons/power_active_icon.svg"; + static const String searchIcon = "assets/icons/search_icon.svg"; + static const String voltageIcon = "assets/icons/voltage_icon.svg"; + static const String speedoMeter = "assets/icons/speedo_meter.svg"; //assets/icons/account_setting.svg static const String accountSetting = 'assets/icons/account_setting.svg'; diff --git a/pubspec.lock b/pubspec.lock index 2c9cb88c..92a76b10 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -137,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "94307bef3a324a0d329d3ab77b2f0c6e5ed739185ffc029ed28c0f9b019ea7ef" + url: "https://pub.dev" + source: hosted + version: "0.69.0" flutter: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index dfeb1ff9..7742e4da 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,7 @@ dependencies: intl: ^0.19.0 dropdown_search: ^5.0.6 flutter_dotenv: ^5.1.0 + fl_chart: ^0.69.0 dev_dependencies: flutter_test: