diff --git a/assets/icons/frequency_icon.svg b/assets/icons/frequency_icon.svg new file mode 100644 index 0000000..d093af3 --- /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 0000000..28b1412 --- /dev/null +++ b/assets/icons/power_active_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/power_clamp.svg b/assets/icons/power_clamp.svg new file mode 100644 index 0000000..5cf2c03 --- /dev/null +++ b/assets/icons/power_clamp.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/search_icon.svg b/assets/icons/search_icon.svg new file mode 100644 index 0000000..e5da4c9 --- /dev/null +++ b/assets/icons/search_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/speedo_meter.svg b/assets/icons/speedo_meter.svg new file mode 100644 index 0000000..be3b5c4 --- /dev/null +++ b/assets/icons/speedo_meter.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/volt_meter_icon.svg b/assets/icons/volt_meter_icon.svg new file mode 100644 index 0000000..97b9037 --- /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 0000000..29b0667 --- /dev/null +++ b/assets/icons/voltage_icon.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/features/devices/bloc/door_sensor_bloc/door_sensor_bloc.dart b/lib/features/devices/bloc/door_sensor_bloc/door_sensor_bloc.dart index 5d39f25..113b9e8 100644 --- a/lib/features/devices/bloc/door_sensor_bloc/door_sensor_bloc.dart +++ b/lib/features/devices/bloc/door_sensor_bloc/door_sensor_bloc.dart @@ -1,5 +1,4 @@ import 'dart:async'; - import 'package:dio/dio.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart b/lib/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart index 686e272..bca98bd 100644 --- a/lib/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart +++ b/lib/features/devices/bloc/garage_door_bloc/garage_door_bloc.dart @@ -171,6 +171,9 @@ class GarageDoorBloc extends Bloc { code: 'switch_1', ); recordGroups = response; + + + emit(UpdateState(garageSensor: deviceStatus)); } on DioException catch (e) { final errorData = e.response!.data; diff --git a/lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart new file mode 100644 index 0000000..48224a1 --- /dev/null +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart @@ -0,0 +1,792 @@ +import 'dart:async'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; +import 'package:syncrow_app/features/devices/bloc/power_clamp_bloc/power_clamp_event.dart'; +import 'package:syncrow_app/features/devices/bloc/power_clamp_bloc/power_clamp_state.dart'; +import 'package:syncrow_app/features/devices/model/device_report_model.dart'; +import 'package:syncrow_app/features/devices/model/power_clamp_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/power_clamp/power_chart.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; + +class PowerClampBloc extends Bloc { + final String PCId; + PowerClampBloc({ + required this.PCId, + }) : super(const PowerClampState()) { + on(_fetchPowerClampInfo); + // on(fetchLogsForLastMonth); + // on(_mapReportToEnergyData); + on(checkDayMonthYearSelected); + on(_filterRecordsByDate); + } + DateTime? dateTime = DateTime.now(); + String formattedDate = DateFormat('yyyy/MM/dd').format(DateTime.now()); + bool lowBattery = false; + bool closingReminder = false; + bool doorAlarm = false; + + PowerClampModel deviceStatus = PowerClampModel( + productType: '', + productUuid: '', + status: PowerStatus( + phaseA: Phase( + dataPoints: [ + DataPoint( + code: '', customName: '', dpId: 0, time: 0, type: '', value: 0), + ], + ), + phaseB: Phase( + dataPoints: [ + DataPoint( + code: '', customName: '', dpId: 0, time: 0, type: '', value: 0), + ], + ), + phaseC: Phase( + dataPoints: [ + DataPoint( + code: '', customName: '', dpId: 0, time: 0, type: '', value: 0), + ], + ), + general: Phase( + dataPoints: [ + DataPoint( + code: '', customName: '', dpId: 0, time: 0, type: '', value: 0), + ], + ), + ), + ); + + void _fetchPowerClampInfo( + PowerClampInitial event, Emitter emit) async { + emit(PowerClampLoadingState()); + try { + var response = await DevicesAPI.getPowerClampStatus(PCId); + PowerClampModel deviceStatus = PowerClampModel.fromJson(response); + emit(UpdateState(powerClampModel: deviceStatus)); + } catch (e) { + emit(PowerClampFailedState(errorMessage: e.toString())); + return; + } + } + + DeviceReport recordGroups = + DeviceReport(startTime: '0', endTime: '0', data: []); + + EventDevice recordGroupsDateTime = + EventDevice(code: '', eventTime: DateTime.now(), value: ''); + + 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'), + ]; + 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( + mainAxisSize: MainAxisSize.min, + 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); + }); + }, + ), + ], + ); + }, + ); + } + + void checkDayMonthYearSelected( + SelectDateEvent event, Emitter emit) async { + emit(PowerClampLoadingState()); + + if (currentIndex == 0) { + await dayMonthYearPicker(context: event.context).then( + (newDate) { + if (newDate != null) { + dateTime = newDate; + formattedDate = DateFormat('yyyy/MM/dd').format(dateTime!); + + add(FilterRecordsByDateEvent( + selectedDate: dateTime!, + viewType: views[currentIndex], + )); + } + }, + ); + } else if (currentIndex == 1) { + await selectMonthAndYear(event.context).then( + (newDate) { + if (newDate != null) { + dateTime = newDate; + formattedDate = DateFormat('yyyy-MM').format(dateTime!); + + add(FilterRecordsByDateEvent( + selectedDate: dateTime!, + viewType: views[currentIndex], + )); + } + }, + ); + } else if (currentIndex == 2) { + await selectYear(event.context).then( + (newDate) { + if (newDate != null) { + dateTime = newDate; + formattedDate = DateFormat('yyyy').format(dateTime!); + + add(FilterRecordsByDateEvent( + selectedDate: dateTime!, + viewType: views[currentIndex], + )); + } + }, + ); + } + emit(DateSelectedState()); + } + + 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 showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return SizedBox( + height: 300, + 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 showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return SizedBox( + height: 300, + 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(); // Pops without value, returning null + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + final selectedDateTime = DateTime(selectedYear); + Navigator.of(context).pop( + selectedDateTime); // Pops with the selected date + }, + ), + ], + ), + ), + ], + ), + ); + }, + ); + } + + Future dayMonthYearPicker({ + required BuildContext context, + }) async { + DateTime selectedDate = DateTime.now(); // Default selected date + + return await showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return SizedBox( + height: 350, // Increased height to accommodate the buttons + child: Column( + children: [ + Expanded( + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.date, + initialDateTime: DateTime.now(), + minimumYear: 1900, + maximumYear: DateTime.now().year, + onDateTimeChanged: (DateTime newDateTime) { + selectedDate = + newDateTime; // Update the selected date when changed + }, + ), + ), + 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(); // Dismiss the modal without returning a value + }, + ), + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context) + .pop(selectedDate); // Return the selected date + }, + ), + ], + ), + ), + ], + ), + ); + }, + ); + } + + List energyDataList = []; + void _filterRecordsByDate( + FilterRecordsByDateEvent event, Emitter emit) { + emit(PowerClampLoadingState()); + if (event.viewType == 'Year') { + filteredRecords = record + .where((record) => record.eventTime!.year == event.selectedDate.year) + .toList(); + } else if (event.viewType == 'Month') { + filteredRecords = record + .where((record) => + record.eventTime!.year == event.selectedDate.year && + record.eventTime!.month == event.selectedDate.month) + .toList(); + } else if (event.viewType == 'Day') { + filteredRecords = record + .where((record) => + record.eventTime!.year == event.selectedDate.year && + record.eventTime!.month == event.selectedDate.month && + record.eventTime!.day == event.selectedDate.day) + .toList(); + } + String getMonthShortName(int month) { + final date = DateTime(0, month); + return DateFormat.MMM().format(date); + } + + 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(); + + Future.delayed(const Duration(milliseconds: 500)); + emit(FilterRecordsState(filteredRecords: energyDataList)); + } +} + +// Event for filtering records by date + + +// _listenToChanges() { +// try { +// DatabaseReference ref = +// FirebaseDatabase.instance.ref('device-status/$PCId'); +// Stream stream = ref.onValue; + +// stream.listen((DatabaseEvent event) async { +// if (_timer != null) { +// await Future.delayed(const Duration(seconds: 2)); +// } +// Map usersMap = +// event.snapshot.value as Map; +// List statusList = []; + +// usersMap['status'].forEach((element) { +// statusList.add(StatusModel(code: element['code'], value: true)); +// }); + +// deviceStatus = PowerClampModel.fromJson(statusList); +// if (!isClosed) { +// add( +// PowerClampSwitch(switchD: deviceStatus.doorContactState), +// ); +// } +// }); +// } catch (_) {} +// } + +// New Function: Convert the device report data into EnergyData and emit it. +// void _mapReportToEnergyData( +// FetchEnergyData event, Emitter emit) { +// try { +// List energyDataList = recordGroups.data +// ?.map((event) { +// if (event.code == "VoltageA" && event.eventTime != null) { +// // Convert eventTime to readable format +// DateTime eventDateTime = +// DateTime.fromMillisecondsSinceEpoch(event.eventTime!); +// String formattedTime = +// "${eventDateTime.hour}:${eventDateTime.minute.toString().padLeft(2, '0')} ${eventDateTime.hour >= 12 ? 'PM' : 'AM'}"; +// double value = double.tryParse(event.value ?? "0") ?? 0; +// return EnergyData( +// formattedTime, value / 1000); // Assume kWh format +// } +// return null; +// }) +// .where((data) => data != null) +// .cast() +// .toList() ?? +// []; + +// emit(EnergyDataState(energyData: energyDataList)); +// } catch (e) { +// emit(PowerClampFailedState(errorMessage: e.toString())); +// } +// } + + // Future selectTimeOfLinePassword( + // SelectDateEvent event, Emitter emit) async { + // emit(ChangeTimeState()); + // final DateTime? picked = await showDatePicker( + // initialDatePickerMode: DatePickerMode.year, + // context: event.context, + // initialDate: DateTime.now(), + // firstDate: DateTime(1905), + // lastDate: DateTime(2101), + // ); + // if (picked != null) { + // final selectedDateTime = DateTime( + // picked.year, + // picked.month, + // picked.day, + // 0, + // 0, + // ); + // final selectedTimestamp = DateTime( + // selectedDateTime.year, + // selectedDateTime.month, + // selectedDateTime.day, + // selectedDateTime.hour, + // selectedDateTime.minute, + // ).millisecondsSinceEpoch ~/ + // 1000; + + // DateTime dateTime = selectedDateTime; + // formattedDate = DateFormat('yyyy/MM/dd').format(dateTime); + // emit(DateSelectedState()); + // } + // } + + // void _fetchStatus( + // PowerClampInitial event, Emitter emit) async { + // emit(PowerClampLoadingState()); + // try { + // var response = await DevicesAPI.getDeviceStatus(PCId); + // List statusModelList = []; + // for (var status in response['status']) { + // statusModelList.add(StatusModel.fromJson(status)); + // } + // deviceStatus = PowerClampModel.fromJson( + // statusModelList, + // ); + // emit(UpdateState(powerClampModel: deviceStatus)); + // Future.delayed(const Duration(milliseconds: 500)); + // // _listenToChanges(); + // } catch (e) { + // emit(PowerClampFailedState(errorMessage: e.toString())); + // return; + // } + // } + + + // Future fetchLogsForLastMonth( + // ReportLogsInitial event, Emitter emit) async { + // DateTime now = DateTime.now(); + // DateTime lastMonth = DateTime(now.year, now.month - 1, now.day); + + // int startTime = lastMonth.millisecondsSinceEpoch; + // int endTime = now.millisecondsSinceEpoch; + + // try { + // emit(PowerClampLoadingState()); + // var response = await DevicesAPI.getReportLogs( + // startTime: startTime.toString(), + // endTime: endTime.toString(), + // deviceUuid: PCId, + // code: event.code!, + // ); + // recordGroups = response; + // record = recordGroups.data!.map((event) { + // return EventDevice( + // code: event.code, + // eventTime: event.eventTime != null + // ? DateTime.fromMillisecondsSinceEpoch(event.eventTime!) + // : null, + // value: event.value, + // ); + // }).toList(); + // for (var event in record) { + // print( + // 'Code: ${event.code}, Event Time: ${DateFormat('yyyy-MM-dd hh:mm:ss a').format(event.eventTime!)}, Value: ${event.value}'); + // } + // emit(UpdateState(powerClampModel: deviceStatus)); + // } on DioException catch (e) { + // final errorData = e.response!.data; + // String errorMessage = errorData['message']; + // } + // } + // int transformTimestamp(int originalTime) { + // DateTime originalDateTime = + // DateTime.fromMillisecondsSinceEpoch(originalTime); + // DateTime transformedDateTime = originalDateTime.add(Duration(hours: 1)); + // return transformedDateTime.millisecondsSinceEpoch; + // } + + // void addDataToRecord(List> rawData) { + // for (var dataPoint in rawData) { + // EventDevice event = EventDevice.fromJson(dataPoint); + // record.add(event); + // } + // } \ No newline at end of file diff --git a/lib/features/devices/bloc/power_clamp_bloc/power_clamp_event.dart b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_event.dart new file mode 100644 index 0000000..ea1fa76 --- /dev/null +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_event.dart @@ -0,0 +1,119 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +abstract class PowerClampEvent extends Equatable { + const PowerClampEvent(); + + @override + List get props => []; +} + +class PowerClampLoading extends PowerClampEvent {} + +class PowerClampSwitch extends PowerClampEvent { + final bool switchD; + final String deviceId; + final String productId; + const PowerClampSwitch( + {required this.switchD, this.deviceId = '', this.productId = ''}); + + @override + List get props => [switchD, deviceId, productId]; +} + +class PowerClampUpdated extends PowerClampEvent {} + +class FetchEnergyData extends PowerClampEvent {} + +class SelectDateEvent extends PowerClampEvent { + BuildContext context; + SelectDateEvent({required this.context}); +} + +class PowerClampInitial extends PowerClampEvent { + const PowerClampInitial(); +} + +class ReportLogsInitial extends PowerClampEvent { + final String? code; + const ReportLogsInitial({required this.code}); + + @override + List get props => [code!]; +} + +class PowerClampChangeStatus extends PowerClampEvent {} + +class GetCounterEvent extends PowerClampEvent { + final String deviceCode; + const GetCounterEvent({required this.deviceCode}); + @override + List get props => [deviceCode]; +} + +class ToggleLowBatteryEvent extends PowerClampEvent { + final bool isLowBatteryEnabled; + + const ToggleLowBatteryEvent(this.isLowBatteryEnabled); + + @override + List get props => [isLowBatteryEnabled]; +} + +class ToggleClosingReminderEvent extends PowerClampEvent { + final bool isClosingReminderEnabled; + + const ToggleClosingReminderEvent(this.isClosingReminderEnabled); + + @override + List get props => [isClosingReminderEnabled]; +} + +class ToggleDoorAlarmEvent extends PowerClampEvent { + final bool isDoorAlarmEnabled; + + const ToggleDoorAlarmEvent(this.isDoorAlarmEnabled); + + @override + List get props => [isDoorAlarmEnabled]; +} + +class SetCounterValue extends PowerClampEvent { + final Duration duration; + final String deviceCode; + const SetCounterValue({required this.duration, required this.deviceCode}); + @override + List get props => [duration, deviceCode]; +} + +class StartTimer extends PowerClampEvent { + final int duration; + + const StartTimer(this.duration); + + @override + List get props => [duration]; +} + +class TickTimer extends PowerClampEvent { + final int remainingTime; + + const TickTimer(this.remainingTime); + + @override + List get props => [remainingTime]; +} + +class StopTimer extends PowerClampEvent {} + +class OnClose extends PowerClampEvent {} + + +class FilterRecordsByDateEvent extends PowerClampEvent { + final DateTime selectedDate; + final String viewType; // 'Day', 'Month', 'Year' + + const FilterRecordsByDateEvent( + {required this.selectedDate, required this.viewType}); +} + diff --git a/lib/features/devices/bloc/power_clamp_bloc/power_clamp_state.dart b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_state.dart new file mode 100644 index 0000000..4e08065 --- /dev/null +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_state.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_app/features/devices/model/power_clamp_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/power_clamp/power_chart.dart'; + +class PowerClampState extends Equatable { + const PowerClampState(); + + @override + List get props => []; +} + +class PowerClampInitialState extends PowerClampState {} + +class PowerClampLoadingState extends PowerClampState {} + +class ChangeTimeState extends PowerClampState {} + +class DateSelectedState extends PowerClampState {} +//DateSelectedState + +class PowerClampFailedState extends PowerClampState { + final String errorMessage; + + const PowerClampFailedState({required this.errorMessage}); + + @override + List get props => [errorMessage]; +} + +class UpdateState extends PowerClampState { + final PowerClampModel powerClampModel; + const UpdateState({required this.powerClampModel}); + + @override + List get props => [powerClampModel]; +} + +class LoadingNewSate extends PowerClampState { + final PowerClampModel powerClampModel; + const LoadingNewSate({required this.powerClampModel}); + + @override + List get props => [powerClampModel]; +} + +class EnergyDataState extends PowerClampState { + final List energyData; + + const EnergyDataState({required this.energyData}); +} + +// State for filtered records +class FilterRecordsState extends PowerClampState { + final List filteredRecords; + + const FilterRecordsState({required this.filteredRecords}); +} diff --git a/lib/features/devices/model/device_model.dart b/lib/features/devices/model/device_model.dart index 65bc021..f1af2f3 100644 --- a/lib/features/devices/model/device_model.dart +++ b/lib/features/devices/model/device_model.dart @@ -76,6 +76,8 @@ class DeviceModel { tempIcon = Assets.gang3touch; } else if (type == DeviceType.WaterLeak) { tempIcon = Assets.waterLeakIcon; + } else if (type == DeviceType.PC) { + tempIcon = Assets.powerClampIcon; } else { tempIcon = Assets.assetsIconsLogo; } diff --git a/lib/features/devices/model/device_report_model.dart b/lib/features/devices/model/device_report_model.dart index c640d81..db09404 100644 --- a/lib/features/devices/model/device_report_model.dart +++ b/lib/features/devices/model/device_report_model.dart @@ -27,6 +27,33 @@ class DeviceReport { }; } +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, + }; +} + + + + + class DeviceEvent { final String? code; final int? eventTime; diff --git a/lib/features/devices/model/power_clamp_model.dart b/lib/features/devices/model/power_clamp_model.dart new file mode 100644 index 0000000..7fb09d4 --- /dev/null +++ b/lib/features/devices/model/power_clamp_model.dart @@ -0,0 +1,86 @@ +// 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']), + ); + } +} + +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/features/devices/view/widgets/power_clamp/power_chart.dart b/lib/features/devices/view/widgets/power_clamp/power_chart.dart new file mode 100644 index 0000000..575337e --- /dev/null +++ b/lib/features/devices/view/widgets/power_clamp/power_chart.dart @@ -0,0 +1,177 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class EnergyConsumptionPage extends StatefulWidget { + final List chartData; + final double totalConsumption; + final String date; + + EnergyConsumptionPage({ + required this.chartData, + required this.totalConsumption, + required this.date, + }); + + @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 Scaffold( + body: Column( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 19), + 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: BorderSide(color: Colors.grey, 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: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + ), + ), + leftTitles: const AxisTitles( + sideTitles: SideTitles( + showTitles: false, + ), + ), + rightTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + ), + ), + topTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + 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.chart.withOpacity(0.6), + show: true, + shadow: Shadow(color: Colors.black12), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + ColorsManager.chart.withOpacity(0.5), + Colors.blue.withOpacity(0.1), + ], + begin: Alignment.center, + end: Alignment.bottomCenter, + ), + ), + dotData: FlDotData( + show: false, + ), + isStrokeCapRound: true, + barWidth: 2, + ), + ], + borderData: FlBorderData( + show: false, + border: Border.all( + color: Color(0xff023DFE).withOpacity(0.7), + width: 10, + ), + ), + ), + ), + ), + ), + ], + ), + ); + } +} + +class EnergyData { + EnergyData(this.time, this.consumption); + final String time; + final double consumption; +} +// \ No newline at end of file diff --git a/lib/features/devices/view/widgets/power_clamp/power_clamp_card.dart b/lib/features/devices/view/widgets/power_clamp/power_clamp_card.dart new file mode 100644 index 0000000..5c758c8 --- /dev/null +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_card.dart @@ -0,0 +1,235 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/view/widgets/power_clamp/power_info_card.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'power_chart.dart'; + +class PowerClampCard extends StatelessWidget { + final bool? isGeneral; + final String? title; + final String? totalCurrent; + final String? totalActiveGeneral; + final String? totalCurrentGeneral; + final String? totalFrequencyGeneral; + final String? totalVoltage; + final String? totalActive; + final String? totalFrequency; + final String? dateTimeSelected; + final String? totalFactor; + final Widget? dateSwitcher; + final String? formattedDate; + final String? energyConsumption; + final Function()? selectDateEvent; + final List? chartData; + final BuildContext? context; + + const PowerClampCard({ + Key? key, + this.isGeneral, + this.title, + this.totalCurrent, + this.totalActiveGeneral, + this.dateTimeSelected, + this.totalCurrentGeneral, + this.totalFrequencyGeneral, + this.totalVoltage, + this.totalActive, + this.totalFrequency, + this.totalFactor, + this.dateSwitcher, + this.formattedDate, + this.selectDateEvent, + this.chartData, + this.context, + this.energyConsumption, + //nConsumption + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return DefaultContainer( + child: Padding( + padding: + const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Energy usage'), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyLarge( + text: title!, + fontSize: 20, + fontWeight: FontWeight.w700, + ), + Row( + children: [ + BodyLarge( + text: energyConsumption!, + fontSize: 20, + fontWeight: FontWeight.w700, + ), + const BodySmall(text: 'kWh') + ], + ), + ], + ), + const SizedBox( + height: 10, + ), + isGeneral == true + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + PowerClampInfoCard( + iconPath: Assets.powerActiveIcon, + title: 'Active', + value: '$totalActiveGeneral', + unit: ' w', + ), + PowerClampInfoCard( + iconPath: Assets.voltMeterIcon, + title: 'Current', + value: '$totalCurrentGeneral', + unit: ' A', + ), + PowerClampInfoCard( + iconPath: Assets.frequencyIcon, + title: 'Frequency', + value: '$totalFrequencyGeneral', + unit: ' Hz', + ), + ], + ) + : Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + PowerClampInfoCard( + iconPath: Assets.voltageIcon, + title: 'Voltage', + value: totalVoltage!, + unit: ' V', + ), + PowerClampInfoCard( + iconPath: Assets.voltMeterIcon, + title: 'Current', + value: '$totalCurrent', + unit: ' A', + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + PowerClampInfoCard( + iconPath: Assets.powerActiveIcon, + title: 'Active Power', + value: '$totalActive', + unit: ' w', + ), + PowerClampInfoCard( + iconPath: Assets.speedoMeter, + title: 'Power Factor', + value: '$totalFactor', + unit: '', + ), + ], + ) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const BodyMedium( + text: 'Total consumption', + fontSize: 12, + fontWeight: FontWeight.w700, + ), + Text( + dateTimeSelected !, + style: const TextStyle( + fontSize: 8, fontWeight: FontWeight.w400), + ), + ], + ), + const Row( + children: [ + BodyMedium( + text: '1000.00 ', + fontSize: 12, + fontWeight: FontWeight.w700), + BodyMedium( + text: 'kWh', + fontSize: 8, + fontWeight: FontWeight.w700), + ], + ), + ], + ), + Expanded( + child: SizedBox( + child: EnergyConsumptionPage( + chartData: chartData!.isNotEmpty + ? chartData! + : [ + 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: chartData! + .fold(0, (sum, data) => sum + data.consumption), + date: '10/08/2024', + ), + ), + ), + const SizedBox( + height: 5, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + DefaultContainer( + padding: EdgeInsets.all(0), + color: ColorsManager.grayBox, + child: SizedBox( + child: dateSwitcher, + )), + InkWell( + onTap: selectDateEvent, + child: DefaultContainer( + color: ColorsManager.grayBox, + child: SizedBox( + child: Padding( + padding: const EdgeInsets.all(5), + child: Text(formattedDate!), + ), + )), + ), + ], + ) + ]), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart b/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart new file mode 100644 index 0000000..506cd2c --- /dev/null +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart @@ -0,0 +1,242 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/power_clamp_bloc/power_clamp_event.dart'; +import 'package:syncrow_app/features/devices/bloc/power_clamp_bloc/power_clamp_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/power_clamp_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/power_clamp/power_chart.dart'; +import 'package:syncrow_app/features/devices/view/widgets/power_clamp/power_clamp_card.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class PowerClampPage extends StatefulWidget { + final DeviceModel? device; + + const PowerClampPage({super.key, this.device}); + + @override + State createState() => _PowerClampPageState(); +} + +class _PowerClampPageState extends State { + final PageController _pageController = PageController(); + int _currentPage = 0; + static const int _pageCount = 4; + late PowerClampModel model; + @override + void initState() { + super.initState(); + _pageController.addListener(_handlePageChange); + model = _initialPowerClampModel(); + } + + void _handlePageChange() { + int nextPage = _pageController.page?.round() ?? 0; + if (_currentPage != nextPage) { + setState(() { + _currentPage = nextPage; + }); + } + } + + @override + void dispose() { + _pageController.removeListener(_handlePageChange); + _pageController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Power Clamp', + child: BlocProvider( + create: (context) => PowerClampBloc(PCId: widget.device?.uuid ?? '') + ..add(const PowerClampInitial()), + // ..add(const ReportLogsInitial(code: 'VoltageA')), + child: BlocBuilder( + builder: (context, state) { + final blocProvider = context.read(); + List chartData = []; + + if (state is UpdateState) { + model = state.powerClampModel; + } else if (state is EnergyDataState) { + chartData = state.energyData; + } else if (state is FilterRecordsState) { + chartData = state.filteredRecords; + } + + if (state is PowerClampLoadingState) { + return const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator(), + ), + ); + } + + return Column( + children: [ + Flexible( + child: RefreshIndicator( + onRefresh: () async { + blocProvider.add(const PowerClampInitial()); + }, + child: PageView(controller: _pageController, children: [ + _buildPowerClampCard( + title: 'Total Energy \nConsumption', + phase: model.status.general, + isGeneral: true, + chartData: chartData, + blocProvider: blocProvider, + ), + _buildPowerClampCard( + title: 'Phase A Energy \nConsumption', + phase: model.status.phaseA, + chartData: chartData, + blocProvider: blocProvider, + ), + _buildPowerClampCard( + title: 'Phase B Energy \nConsumption', + phase: model.status.phaseB, + chartData: chartData, + blocProvider: blocProvider, + ), + _buildPowerClampCard( + title: 'Phase C Energy \nConsumption', + phase: model.status.phaseC, + chartData: chartData, + blocProvider: blocProvider, + ), + ]), + ), + ), + _buildPageIndicator(), + ], + ); + }, + ), + ), + ); + } + + PowerClampModel _initialPowerClampModel() { + return PowerClampModel( + productType: '', + productUuid: '', + status: PowerStatus( + phaseA: Phase(dataPoints: _emptyDataPoints()), + phaseB: Phase(dataPoints: _emptyDataPoints()), + phaseC: Phase(dataPoints: _emptyDataPoints()), + general: Phase(dataPoints: _emptyDataPoints()), + ), + ); + } + + List _emptyDataPoints() { + return [ + DataPoint( + code: '', + customName: '', + dpId: 0, + time: 0, + type: '', + value: 0, + ), + ]; + } + + List _buildPowerClampCards(PowerClampModel model, + List chartData, PowerClampBloc blocProvider) { + return [ + _buildPowerClampCard( + title: 'Total Energy \nConsumption', + phase: model.status.general, + isGeneral: true, + chartData: chartData, + blocProvider: blocProvider, + ), + _buildPowerClampCard( + title: 'Phase A Energy \nConsumption', + phase: model.status.phaseA, + chartData: chartData, + blocProvider: blocProvider, + ), + _buildPowerClampCard( + title: 'Phase B Energy \nConsumption', + phase: model.status.phaseB, + chartData: chartData, + blocProvider: blocProvider, + ), + _buildPowerClampCard( + title: 'Phase C Energy \nConsumption', + phase: model.status.phaseC, + chartData: chartData, + blocProvider: blocProvider, + ), + ]; + } + + Widget _buildPowerClampCard({ + required String title, + required Phase phase, + bool isGeneral = false, + required List chartData, + required PowerClampBloc blocProvider, + }) { + return PowerClampCard( + dateTimeSelected:blocProvider.formattedDate, + energyConsumption: _getValueOrNA(phase.dataPoints, isGeneral ? 0 : 5), + title: title, + isGeneral: isGeneral, + dateSwitcher: blocProvider.dateSwitcher(), + formattedDate: blocProvider.formattedDate, + selectDateEvent: () { + blocProvider.add(SelectDateEvent(context: context)); + }, + totalActiveGeneral: isGeneral ? _getValueOrNA(phase.dataPoints, 2) : null, + totalCurrentGeneral: + isGeneral ? _getValueOrNA(phase.dataPoints, 1) : null, + totalFrequencyGeneral: + isGeneral ? _getValueOrNA(phase.dataPoints, 4) : null, + totalFactor: !isGeneral ? _getValueOrNA(phase.dataPoints, 3) : null, + totalActive: !isGeneral ? _getValueOrNA(phase.dataPoints, 2) : null, + totalCurrent: !isGeneral ? _getValueOrNA(phase.dataPoints, 1) : null, + totalVoltage: !isGeneral ? _getValueOrNA(phase.dataPoints, 0) : null, + chartData: chartData, + context: context, + ); + } + + String _getValueOrNA(List dataPoints, int index) { + return dataPoints.length > index + ? dataPoints[index].value.toString() + : 'N/A'; + } + + Widget _buildPageIndicator() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(_pageCount, (index) { + return AnimatedContainer( + duration: const Duration(milliseconds: 300), + margin: const EdgeInsets.symmetric(horizontal: 4.0), + height: 10.0, + width: _currentPage == index ? 10.0 : 10.0, + decoration: BoxDecoration( + color: + _currentPage == index ? Colors.grey : ColorsManager.greyColor, + borderRadius: BorderRadius.circular(5.0), + ), + ); + }), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/power_clamp/power_clamp_test.dart b/lib/features/devices/view/widgets/power_clamp/power_clamp_test.dart new file mode 100644 index 0000000..4c0cab1 --- /dev/null +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_test.dart @@ -0,0 +1,312 @@ +// import 'package:flutter/material.dart'; +// import 'package:flutter_bloc/flutter_bloc.dart'; +// import 'package:syncrow_app/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart'; +// import 'package:syncrow_app/features/devices/bloc/power_clamp_bloc/power_clamp_event.dart'; +// import 'package:syncrow_app/features/devices/bloc/power_clamp_bloc/power_clamp_state.dart'; +// import 'package:syncrow_app/features/devices/model/device_model.dart'; +// import 'package:syncrow_app/features/devices/model/power_clamp_model.dart'; +// import 'package:syncrow_app/features/devices/view/widgets/power_clamp/power_chart.dart'; +// import 'package:syncrow_app/features/devices/view/widgets/power_clamp/power_clamp_card.dart'; +// import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +// import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +// import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +// class PowerClampTestPage extends StatefulWidget { +// late final DeviceModel? device; + +// PowerClampTestPage({super.key, this.device}); + +// @override +// _PowerClampTestPageState createState() => _PowerClampTestPageState(); +// } + +// class _PowerClampTestPageState extends State { +// final PageController _pageController = PageController(); +// int _currentPage = 0; +// final int _pageCount = 4; +// @override +// void initState() { +// super.initState(); +// _pageController.addListener(() { +// int nextPage = _pageController.page?.round() ?? 0; +// if (_currentPage != nextPage) { +// setState(() { +// _currentPage = nextPage; +// }); +// } +// }); +// } + +// @override +// void dispose() { +// _pageController.dispose(); +// super.dispose(); +// } + +// @override +// Widget build(BuildContext context) { +// return DefaultScaffold( +// title: 'Power Clamp', +// child: BlocProvider( +// create: (context) => PowerClampBloc(PCId: widget.device?.uuid ?? '') +// ..add(const PowerClampInitial()), +// child: BlocBuilder( +// builder: (context, state) { +// final _blocProvider = BlocProvider.of(context); +// PowerClampModel model = PowerClampModel( +// productType: '', +// productUuid: '', +// status: PowerStatus( +// phaseA: Phase( +// dataPoints: [ +// DataPoint( +// code: '', +// customName: '', +// dpId: 0, +// time: 0, +// type: '', +// value: 0), +// ], +// ), +// phaseB: Phase( +// dataPoints: [ +// DataPoint( +// code: '', +// customName: '', +// dpId: 0, +// time: 0, +// type: '', +// value: 0), +// ], +// ), +// phaseC: Phase( +// dataPoints: [ +// DataPoint( +// code: '', +// customName: '', +// dpId: 0, +// time: 0, +// type: '', +// value: 0), +// ], +// ), +// general: Phase( +// dataPoints: [ +// DataPoint( +// code: '', +// customName: '', +// dpId: 0, +// time: 0, +// type: '', +// value: 0), +// ], +// ), +// ), +// ); +// List chartData = []; + +// if (state is UpdateState) { +// model = state.powerClampModel; +// } else if (state is EnergyDataState) { +// chartData = state.energyData; +// } +// return state is PowerClampLoadingState +// ? const Center( +// child: DefaultContainer( +// width: 50, +// height: 50, +// child: CircularProgressIndicator()), +// ) +// : Column( +// children: [ +// Flexible( +// child: RefreshIndicator( +// onRefresh: () async { +// _blocProvider.add(const PowerClampInitial()); +// }, +// child: PageView( +// controller: _pageController, +// children: [ +// PowerClampCard( +// energyConsumption: +// model.status.general.dataPoints.length > 0 +// ? model.status.general.dataPoints[0].value +// .toString() +// : 'N/A', +// title: 'Total Energy \nConsumption', +// isGeneral: true, +// dateSwitcher: Container(), +// formattedDate: _blocProvider.formattedDate, +// selectDateEvent: () { +// _blocProvider.add( +// SelectDateEvent(context: context)); +// }, +// totalFrequencyGeneral: +// model.status.general.dataPoints.length > 4 +// ? model.status.general.dataPoints[4].value +// .toString() +// : 'N/A', +// totalActiveGeneral: +// model.status.general.dataPoints.length > 2 +// ? model.status.general.dataPoints[2].value +// .toString() +// : 'N/A', +// totalCurrentGeneral: +// model.status.general.dataPoints.length > 1 +// ? model.status.general.dataPoints[1].value +// .toString() +// : 'N/A', +// totalVoltage: +// model.status.general.dataPoints.length > 0 +// ? model.status.general.dataPoints[0].value.toString() +// : 'N/A', +// chartData: chartData, +// context: context), + + + +// PowerClampCard( +// energyConsumption: +// model.status.phaseA.dataPoints.length > 5 +// ? model.status.phaseA.dataPoints[5].value +// .toString() +// : 'N/A', +// title: 'Phase A Energy \nConsumption', +// dateSwitcher: _blocProvider.dateSwitcher(), +// formattedDate: _blocProvider.formattedDate, +// selectDateEvent: () { +// _blocProvider.add( +// SelectDateEvent(context: context)); +// }, +// totalFactor: model.status.phaseA.dataPoints.length > 3 +// ? model.status.phaseA.dataPoints[3].value +// .toString() +// : 'N/A', +// totalActive: model.status.phaseA.dataPoints.length > 2 +// ? model.status.phaseA.dataPoints[2].value +// .toString() +// : 'N/A', +// totalCurrent: +// model.status.phaseA.dataPoints.length > 1 +// ? model.status.phaseA.dataPoints[1] +// .value +// .toString() +// : 'N/A', +// totalVoltage: +// model.status.phaseA.dataPoints.length > 0 +// ? model.status.phaseA.dataPoints[0] +// .value +// .toString() +// : 'N/A', +// chartData: chartData, +// context: context), + + +// PowerClampCard( +// energyConsumption: +// model.status.phaseB.dataPoints.length > 5 +// ? model.status.phaseB.dataPoints[5].value +// .toString() +// : 'N/A', +// title: 'Phase B Energy \nConsumption', +// dateSwitcher: _blocProvider.dateSwitcher(), +// formattedDate: _blocProvider.formattedDate, +// selectDateEvent: () { +// _blocProvider.add( +// SelectDateEvent(context: context)); +// }, +// totalFactor: model.status.phaseA.dataPoints.length > 3 +// ? model.status.phaseB.dataPoints[3].value +// .toString() +// : 'N/A', +// totalActive: model.status.phaseB.dataPoints.length > 2 +// ? model.status.phaseB.dataPoints[2].value +// .toString() +// : 'N/A', +// totalCurrent: +// model.status.phaseB.dataPoints.length > 1 +// ? model.status.phaseB.dataPoints[1] +// .value +// .toString() +// : 'N/A', +// totalVoltage: +// model.status.phaseB.dataPoints.length > 0 +// ? model.status.phaseB.dataPoints[0] +// .value +// .toString() +// : 'N/A', +// chartData: chartData, +// context: context), + + +// PowerClampCard( +// energyConsumption: +// model.status.phaseC.dataPoints.length > 5 +// ? model.status.phaseC.dataPoints[5].value +// .toString() +// : 'N/A', +// title: 'Phase A Energy \nConsumption', +// dateSwitcher: _blocProvider.dateSwitcher(), +// formattedDate: _blocProvider.formattedDate, +// selectDateEvent: () { +// _blocProvider.add( +// SelectDateEvent(context: context)); +// }, +// totalFactor: model.status.phaseC.dataPoints.length > 3 +// ? model.status.phaseC.dataPoints[3].value +// .toString() +// : 'N/A', +// totalActive: model.status.phaseC.dataPoints.length > 2 +// ? model.status.phaseC.dataPoints[2].value +// .toString() +// : 'N/A', +// totalCurrent: +// model.status.phaseC.dataPoints.length > 1 +// ? model.status.phaseC.dataPoints[1] +// .value +// .toString() +// : 'N/A', +// totalVoltage: +// model.status.phaseC.dataPoints.length > 0 +// ? model.status.phaseC.dataPoints[0] +// .value +// .toString() +// : 'N/A', +// chartData: chartData, +// context: context), + +// ]), +// ), +// ), +// Padding( +// padding: const EdgeInsets.symmetric(vertical: 10.0), +// child: Row( +// mainAxisAlignment: MainAxisAlignment.center, +// children: List.generate(_pageCount, (index) { +// return AnimatedContainer( +// duration: const Duration(milliseconds: 300), +// margin: +// const EdgeInsets.symmetric(horizontal: 4.0), +// height: 10.0, +// width: _currentPage == index +// ? 10.0 +// : 10.0, // Change width for current page +// decoration: BoxDecoration( +// color: _currentPage == index +// ? Colors +// .grey // Use a different color for the active indicator +// : ColorsManager.greyColor, +// borderRadius: BorderRadius.circular(5.0), +// ), +// ); +// }), +// ), +// ), +// ], +// ); +// }, +// ), +// ), +// ); +// } +// } diff --git a/lib/features/devices/view/widgets/power_clamp/power_info_card.dart b/lib/features/devices/view/widgets/power_clamp/power_info_card.dart new file mode 100644 index 0000000..04d21e6 --- /dev/null +++ b/lib/features/devices/view/widgets/power_clamp/power_info_card.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/shared_widgets/default_container.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/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: DefaultContainer( + height: 55, + color: ColorsManager.grayBox, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: SvgPicture.asset( + iconPath, + fit: BoxFit.fill, + ), + ), + Expanded( + flex: 3, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + BodyMedium( + fontWeight: FontWeight.w400, + fontSize: 8, + text: title, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + BodyMedium( + fontWeight: FontWeight.w700, + fontSize: 15, + text: value, + ), + BodyMedium( + fontWeight: FontWeight.w700, + fontSize: 8, + text: unit, + ), + ], + ), + ], + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/devices/view/widgets/room_page.dart b/lib/features/devices/view/widgets/room_page.dart index ce2028a..86fbed9 100644 --- a/lib/features/devices/view/widgets/room_page.dart +++ b/lib/features/devices/view/widgets/room_page.dart @@ -1,30 +1,88 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_app/features/devices/model/room_model.dart'; import 'package:syncrow_app/features/devices/view/widgets/room_page_switch.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; -class RoomPage extends StatelessWidget { +class RoomPage extends StatefulWidget { const RoomPage({super.key, required this.room}); final RoomModel room; + @override + _RoomPageState createState() => _RoomPageState(); +} + +class _RoomPageState extends State { + final TextEditingController _searchController = TextEditingController(); + List _filteredDevices = []; + + @override + void initState() { + super.initState(); + _filteredDevices = widget.room.devices ?? []; + _searchController.addListener(_filterDevices); + } + + @override + void dispose() { + _searchController.removeListener(_filterDevices); + _searchController.dispose(); + super.dispose(); + } + + void _filterDevices() { + final query = _searchController.text.toLowerCase(); + setState(() { + _filteredDevices = widget.room.devices! + .where((device) => device.type!.toLowerCase().contains(query)) + .toList(); + }); + } + @override Widget build(BuildContext context) { - return SingleChildScrollView( - child: GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 10, - mainAxisSpacing: 10, - childAspectRatio: 1.5, + return Column( + children: [ + if (widget.room.devices!.isNotEmpty) + TextFormField( + controller: _searchController, + decoration: InputDecoration( + hintText: 'Search', + hintStyle: const TextStyle( + color: ColorsManager.textGray, + fontSize: 16, + fontWeight: FontWeight.w400), + prefixIcon: Container( + padding: const EdgeInsets.all(5.0), + margin: const EdgeInsets.all(10.0), + child: SvgPicture.asset( + Assets.searchIcon, + fit: BoxFit.contain, + ), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + ), + Expanded( + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + childAspectRatio: 1.5, + ), + padding: const EdgeInsets.only(top: 10), + itemCount: _filteredDevices.length, + itemBuilder: (context, index) { + return RoomPageSwitch(device: _filteredDevices[index]); + }, + ), ), - padding: const EdgeInsets.only(top: 10), - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: room.devices!.length, - itemBuilder: (context, index) { - return RoomPageSwitch(device: room.devices![index]); - }, - ), + ], ); } } diff --git a/lib/features/devices/view/widgets/room_page_switch.dart b/lib/features/devices/view/widgets/room_page_switch.dart index 8e06f4d..15b86eb 100644 --- a/lib/features/devices/view/widgets/room_page_switch.dart +++ b/lib/features/devices/view/widgets/room_page_switch.dart @@ -15,6 +15,7 @@ import 'package:syncrow_app/features/devices/view/widgets/gateway/gateway_view.d import 'package:syncrow_app/features/devices/view/widgets/lights/light_interface.dart'; import 'package:syncrow_app/features/devices/view/widgets/one_gang/one_gang_Interface.dart'; import 'package:syncrow_app/features/devices/view/widgets/one_touch/one_touch_screen.dart'; +import 'package:syncrow_app/features/devices/view/widgets/power_clamp/power_clamp_page.dart'; import 'package:syncrow_app/features/devices/view/widgets/three_touch/three_touch_interface.dart'; import 'package:syncrow_app/features/devices/view/widgets/two_gang/two_gang_Interface.dart'; import 'package:syncrow_app/features/devices/view/widgets/two_touch/two_touch_Interface.dart'; @@ -170,6 +171,13 @@ void showDeviceInterface(DeviceModel device, BuildContext context) { pageBuilder: (context, animation1, animation2) => DoorSensorScreen(device: device))); + case DeviceType.PC: + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + PowerClampPage(device: device))); + case DeviceType.OneTouch: Navigator.push( context, diff --git a/lib/features/devices/view/widgets/wizard_page.dart b/lib/features/devices/view/widgets/wizard_page.dart index 4409a24..375cdf2 100644 --- a/lib/features/devices/view/widgets/wizard_page.dart +++ b/lib/features/devices/view/widgets/wizard_page.dart @@ -13,7 +13,9 @@ import 'package:syncrow_app/features/devices/view/widgets/two_touch/two_touch_wi import 'package:syncrow_app/features/devices/view/widgets/water_heater/wh_wizard.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; class WizardPage extends StatelessWidget { final List groupsList; @@ -21,126 +23,171 @@ class WizardPage extends StatelessWidget { @override Widget build(BuildContext context) { - return GridView.builder( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 10, - mainAxisSpacing: 10, - childAspectRatio: 1.5, - ), - padding: const EdgeInsets.only(top: 10), - shrinkWrap: true, - itemCount: groupsList.length, - itemBuilder: (_, index) { - return GestureDetector( - onTap: () { - if (groupsList[index].name == 'AC') { - Navigator.push( - context, - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => - const ACsView())); - } - if (groupsList[index].name == '3G') { - Navigator.push( - context, - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => - const ThreeGangWizard())); - } - if (groupsList[index].name == '2G') { - Navigator.push( - context, - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => - const TwoGangWizard())); - } - if (groupsList[index].name == '1G') { - Navigator.push( - context, - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => - const OneGangWizard())); - } - if (groupsList[index].name == 'WH') { - Navigator.push( - context, - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => - const WHWizard())); - } + final TextEditingController _searchController = TextEditingController(); + List _filteredGroups = groupsList; - if (groupsList[index].name == '1GT') { - Navigator.push( - context, - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => - const OneTouchWizard())); - } + void _filterGroups(String query) { + _filteredGroups = groupsList + .where((group) => + group.name!.toLowerCase().contains(query.toLowerCase())) + .toList(); + } - if (groupsList[index].name == '2GT') { - Navigator.push( - context, - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => - const TwoTouchWizard())); - } - - if (groupsList[index].name == '3GT') { - Navigator.push( - context, - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => - const ThreeTouchWizard())); - } - - if (groupsList[index].name == 'GD') { - Navigator.push( - context, - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => - const GarageWizard())); - } - if (groupsList[index].name == 'CUR') { - Navigator.push( - context, - PageRouteBuilder( - pageBuilder: (context, animation1, animation2) => - const CurtainsWizard())); - } - }, - child: DefaultContainer( - padding: const EdgeInsets.all(15), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SvgPicture.asset( - groupsList[index].icon!, + return StatefulBuilder( + builder: (context, setState) { + return ListView( + children: [ + if (groupsList.isNotEmpty) + TextFormField( + controller: _searchController, + onChanged: (value) { + setState(() { + _filterGroups(value); + }); + }, + decoration: InputDecoration( + hintText: 'Search', + hintStyle: const TextStyle( + color: ColorsManager.textGray, + fontSize: 16, + fontWeight: FontWeight.w400), + prefixIcon: Container( + padding: const EdgeInsets.all(5.0), + margin: const EdgeInsets.all(10.0), + child: SvgPicture.asset( + Assets.searchIcon, fit: BoxFit.contain, ), - // CustomSwitch( - ], - ), - FittedBox( - fit: BoxFit.scaleDown, - child: BodyLarge( - text: groupsList[index].name!, - style: context.bodyLarge.copyWith( - fontWeight: FontWeight.bold, - height: 0, - fontSize: 20, - color: Colors.grey, - ), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8.0), ), ), - ], + ), + GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + childAspectRatio: 1.5, + ), + padding: const EdgeInsets.only(top: 10), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: _filteredGroups.length, + itemBuilder: (_, index) { + return GestureDetector( + onTap: () { + if (_filteredGroups[index].name == 'AC') { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const ACsView())); + } + if (_filteredGroups[index].name == '3G') { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const ThreeGangWizard())); + } + if (_filteredGroups[index].name == '2G') { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const TwoGangWizard())); + } + if (_filteredGroups[index].name == '1G') { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const OneGangWizard())); + } + if (_filteredGroups[index].name == 'WH') { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const WHWizard())); + } + + if (_filteredGroups[index].name == '1GT') { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const OneTouchWizard())); + } + + if (_filteredGroups[index].name == '2GT') { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const TwoTouchWizard())); + } + + if (_filteredGroups[index].name == '3GT') { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const ThreeTouchWizard())); + } + + if (_filteredGroups[index].name == 'GD') { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const GarageWizard())); + } + if (_filteredGroups[index].name == 'CUR') { + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + const CurtainsWizard())); + } + }, + child: DefaultContainer( + padding: const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SvgPicture.asset( + _filteredGroups[index].icon!, + fit: BoxFit.contain, + ), + ], + ), + FittedBox( + fit: BoxFit.scaleDown, + child: BodyLarge( + text: _filteredGroups[index].name!, + style: context.bodyLarge.copyWith( + fontWeight: FontWeight.bold, + height: 0, + fontSize: 20, + color: Colors.grey, + ), + ), + ), + ], + ), + ), + ); + }, ), - ), + ], ); }, ); diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index acf46f7..2d7e20a 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -1081,5 +1081,14 @@ class Assets { static const String gang1touch = "assets/icons/1gang_touch.svg"; static const String gang2touch = "assets/icons/2gang_touch.svg"; static const String gang3touch = "assets/icons/3gang_touch.svg"; - //leakNormalIcon + + 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"; + static const String powerClampIcon = "assets/icons/power_clamp.svg"; + + //powerClampIcon } diff --git a/lib/services/api/api_links_endpoints.dart b/lib/services/api/api_links_endpoints.dart index 238bdf6..422d312 100644 --- a/lib/services/api/api_links_endpoints.dart +++ b/lib/services/api/api_links_endpoints.dart @@ -105,8 +105,8 @@ abstract class ApiEndpoints { static const String deviceByUuid = '/device/{deviceUuid}'; static const String deviceFunctions = '/device/{deviceUuid}/functions'; static const String gatewayApi = '/device/gateway/{gatewayUuid}/devices'; - static const String deviceFunctionsStatus = - '/device/{deviceUuid}/functions/status'; + static const String deviceFunctionsStatus = '/device/{deviceUuid}/functions/status'; + static const String powerClamp = '/device/{powerClampUuid}/power-clamp/status'; ///Device Permission Module //POST diff --git a/lib/services/api/devices_api.dart b/lib/services/api/devices_api.dart index 8d096f7..87fcdb6 100644 --- a/lib/services/api/devices_api.dart +++ b/lib/services/api/devices_api.dart @@ -80,6 +80,18 @@ class DevicesAPI { return response; } + static Future> getPowerClampStatus( + String deviceId) async { + final response = await _httpService.get( + path: ApiEndpoints.powerClamp.replaceAll('{powerClampUuid}', deviceId), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } + static Future> renamePass( {required String name, required String doorLockUuid, @@ -371,6 +383,7 @@ class DevicesAPI { .replaceAll('{startTime}', startTime) .replaceAll('{endTime}', endTime), expectedResponseModel: (json) { + log('json=====$json'); return DeviceReport.fromJson(json); }, ); diff --git a/lib/utils/resource_manager/color_manager.dart b/lib/utils/resource_manager/color_manager.dart index 78e9a50..c078eab 100644 --- a/lib/utils/resource_manager/color_manager.dart +++ b/lib/utils/resource_manager/color_manager.dart @@ -29,4 +29,7 @@ abstract class ColorsManager { static const Color graysColor = Color(0xffEBEBEB); static const Color textGray = Color(0xffD5D5D5); static const Color switchButton = Color(0xff023DFE); + static const Color grayBox = Color(0xffF5F5F5); + static const Color chart = Color(0xff023DFE); } +//background: #F5F5F5;023DFE diff --git a/lib/utils/resource_manager/constants.dart b/lib/utils/resource_manager/constants.dart index 4542cba..4b54f15 100644 --- a/lib/utils/resource_manager/constants.dart +++ b/lib/utils/resource_manager/constants.dart @@ -55,7 +55,7 @@ enum DeviceType { ThreeTouch, GarageDoor, WaterLeak, - + PC, Other, } @@ -87,6 +87,7 @@ Map devicesTypesMap = { "3GT": DeviceType.ThreeTouch, "GD": DeviceType.GarageDoor, "WL": DeviceType.WaterLeak, + "PC": DeviceType.PC, }; Map> devicesFunctionsMap = { DeviceType.AC: [ @@ -471,6 +472,43 @@ Map> devicesFunctionsMap = { })), ], DeviceType.WaterLeak: [], + DeviceType.PC: [ + FunctionModel( + code: 'switch_1', + type: functionTypesMap['Boolean'], + values: ValueModel.fromJson({})), + FunctionModel( + code: 'countdown_1', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson( + {"unit": "s", "min": 0, "max": 86400, "scale": 0, "step": 1})), + FunctionModel( + code: 'tr_timecon', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson( + {"unit": "s", "min": 0, "max": 120, "scale": 0, "step": 1})), + FunctionModel( + code: 'countdown_alarm', + type: functionTypesMap['Integer'], + values: ValueModel.fromJson( + {"unit": "s", "min": 0, "max": 86400, "scale": 0, "step": 1})), + FunctionModel( + code: 'door_control_1', + type: functionTypesMap['Enum'], + values: ValueModel.fromJson({ + "range": ['open', 'open'] + })), + FunctionModel( + code: 'voice_control_1', + type: functionTypesMap['Boolean'], + values: ValueModel.fromJson({})), + FunctionModel( + code: 'door_state_1', + type: functionTypesMap['Enum'], + values: ValueModel.fromJson({ + "range": ["unclosed_time", "close_time_alarm", "none"] + })), + ], }; enum TempModes { hot, cold, wind } diff --git a/pubspec.lock b/pubspec.lock index 935996a..4b11f46 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -317,10 +317,10 @@ packages: dependency: "direct main" description: name: fl_chart - sha256: "00b74ae680df6b1135bdbea00a7d1fc072a9180b7c3f3702e4b19a9943f5ed7d" + sha256: "94307bef3a324a0d329d3ab77b2f0c6e5ed739185ffc029ed28c0f9b019ea7ef" url: "https://pub.dev" source: hosted - version: "0.66.2" + version: "0.69.0" flutter: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 4dff6df..a7270f2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,6 @@ dependencies: firebase_analytics: ^10.8.7 firebase_core: ^2.25.5 firebase_crashlytics: ^3.4.16 - fl_chart: ^0.66.2 flutter: sdk: flutter flutter_animated_dialog: ^2.0.1 @@ -45,8 +44,10 @@ dependencies: time_picker_spinner: ^1.0.0 image_picker: ^1.1.2 device_info_plus: ^10.1.0 + fl_chart: ^0.69.0 firebase_database: ^10.5.7 + dev_dependencies: flutter_lints: ^3.0.1 flutter_test: