From e0dbbd67d11af006518a9817b1c93ccbf15ef9d5 Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 14 Oct 2024 16:14:17 +0300 Subject: [PATCH 01/12] static power clamp --- assets/icons/frequency_icon.svg | 25 ++ assets/icons/power_active_icon.svg | 3 + assets/icons/volt_meter_icon.svg | 26 ++ .../view/widgets/power_clamp/power_chart.dart | 192 ++++++++++++++ .../widgets/power_clamp/power_clamp_page.dart | 235 ++++++++++++++++++ .../widgets/power_clamp/power_info_card.dart | 74 ++++++ .../view/widgets/room_page_switch.dart | 13 +- lib/generated/assets.dart | 5 + lib/utils/resource_manager/color_manager.dart | 3 + pubspec.lock | 4 +- pubspec.yaml | 3 +- 11 files changed, 578 insertions(+), 5 deletions(-) create mode 100644 assets/icons/frequency_icon.svg create mode 100644 assets/icons/power_active_icon.svg create mode 100644 assets/icons/volt_meter_icon.svg create mode 100644 lib/features/devices/view/widgets/power_clamp/power_chart.dart create mode 100644 lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart create mode 100644 lib/features/devices/view/widgets/power_clamp/power_info_card.dart 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/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/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..673ef1a --- /dev/null +++ b/lib/features/devices/view/widgets/power_clamp/power_chart.dart @@ -0,0 +1,192 @@ +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() { + // Use the provided data + _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, + getTitlesWidget: (value, meta) { + int index = value.toInt(); + if (index >= 0 && index < _chartData.length) { + return Text(_chartData[index].time, + style: TextStyle(fontSize: 10)); + } + return const SizedBox.shrink(); + }, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + ), + ), + rightTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: false, + reservedSize: 40, + getTitlesWidget: (value, meta) { + return Text(value.toStringAsFixed(1) + ' kWh', + style: TextStyle(fontSize: 10)); + }, + ), + ), + 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.blue.withOpacity(0.2), + dashArray: [10, 10], + strokeWidth: 1, + ); + }, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Colors.grey.withOpacity(0.2), + dashArray: [5, 5], + strokeWidth: 1, + ); + }, + drawHorizontalLine: false, + ), + lineBarsData: [ + LineChartBarData( + + 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: 6, + + ), + ], + 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_page.dart b/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart new file mode 100644 index 0000000..5fd0fc0 --- /dev/null +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart @@ -0,0 +1,235 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/devices/bloc/door_sensor_bloc/door_sensor_bloc.dart'; +import 'package:syncrow_app/features/devices/bloc/door_sensor_bloc/door_sensor_event.dart'; +import 'package:syncrow_app/features/devices/bloc/door_sensor_bloc/door_sensor_state.dart'; +import 'package:syncrow_app/features/devices/model/device_model.dart'; +import 'package:syncrow_app/features/devices/model/door_sensor_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_info_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/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'; + +class PowerClampPage extends StatefulWidget { + final DeviceModel? device; + + const PowerClampPage({super.key, this.device}); + + @override + _PowerClampPageState createState() => _PowerClampPageState(); +} + +class _PowerClampPageState extends State { + final PageController _pageController = PageController(); + int _currentPage = 0; + final int _pageCount = 5; + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Power Clamp', + child: BlocProvider( + create: (context) => DoorSensorBloc(DSId: widget.device?.uuid ?? '') + ..add(const DoorSensorInitial()), + child: BlocBuilder( + builder: (context, state) { + final doorSensorBloc = BlocProvider.of(context); + DoorSensorModel model = + DoorSensorModel(batteryPercentage: 0, doorContactState: false); + if (state is LoadingNewSate) { + model = state.doorSensor; + } else if (state is UpdateState) { + model = state.doorSensor; + } + return state is DoorSensorLoadingState + ? const Center( + child: DefaultContainer( + width: 50, + height: 50, + child: CircularProgressIndicator()), + ) + : Column( + children: [ + Expanded( + child: RefreshIndicator( + onRefresh: () async { + doorSensorBloc.add(const DoorSensorInitial()); + }, + child: PageView.builder( + controller: _pageController, + onPageChanged: (int page) { + setState(() { + _currentPage = page; + }); + }, + itemBuilder: (context, index) { + return DefaultContainer( + child: Padding( + padding: const EdgeInsets.only( + left: 10, right: 10, top: 25, bottom: 20), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text('Energy usage'), + Row( + crossAxisAlignment: + CrossAxisAlignment.end, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + BodyLarge( + text: 'Total Energy \nConsumption', + fontSize: 20, + fontWeight: FontWeight.w700, + ), + Row( + children: [ + BodyLarge( + text: '8623.20 ', + fontSize: 20, + fontWeight: FontWeight.w700, + ), + BodySmall(text: 'kWh') + ], + ), + ], + ), + SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + PowerClampInfoCard( + iconPath: Assets.powerActiveIcon, + title: 'Active', + value: '700', + unit: ' w', + ), + PowerClampInfoCard( + iconPath: Assets.voltMeterIcon, + title: 'Current', + value: '3.06', + unit: ' A', + ), + PowerClampInfoCard( + iconPath: Assets.frequencyIcon, + title: 'Frequency', + value: '50', + unit: ' Hz', + ), + ], + ), + SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + BodyMedium( + text: 'Total consumption', + fontSize: 12, + fontWeight: FontWeight.w700, + ), + Text( + '10/08/2024', + style: TextStyle( + fontSize: 8, + fontWeight: + FontWeight.w400), + ), + ], + ), + Row( + children: [ + BodyMedium( + text: '1000.00 ', + fontSize: 12, + fontWeight: FontWeight.w700), + BodyMedium( + text: 'kWh', + fontSize: 8, + fontWeight: FontWeight.w700), + ], + ), + ], + ), + SizedBox( + height: 310, + child: EnergyConsumptionPage( + 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: 100.0, + date: '10/08/2024', + ), + ), + ], + ), + ), + ); + }, + itemCount: _pageCount, + ), + ), + ), + 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), + ), + child: Center( + child: _currentPage == index + ? Text( + '', + ) + : SizedBox.shrink(), + ), + ); + }), + ), + ), + ], + ); + }, + ), + ), + ); + } +} 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..a79adb0 --- /dev/null +++ b/lib/features/devices/view/widgets/power_clamp/power_info_card.dart @@ -0,0 +1,74 @@ +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(8.0), + child: DefaultContainer( + height: 60, + color: ColorsManager.grayBox, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: SvgPicture.asset( + iconPath, + fit: BoxFit.fill, + ), + ), + Expanded( + flex: 3, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + BodyMedium( + fontWeight: FontWeight.w400, + fontSize: 8, + text: title, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + 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_switch.dart b/lib/features/devices/view/widgets/room_page_switch.dart index 8e06f4d..bbe4bed 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'; @@ -163,12 +164,20 @@ void showDeviceInterface(DeviceModel device, BuildContext context) { PageRouteBuilder( pageBuilder: (context, animation1, animation2) => WaterHeaterPage(device: device))); - case DeviceType.DS: + // case DeviceType.DS: + // Navigator.push( + // context, + // PageRouteBuilder( + // pageBuilder: (context, animation1, animation2) => + // DoorSensorScreen(device: device))); + + + case DeviceType.DS: Navigator.push( context, PageRouteBuilder( pageBuilder: (context, animation1, animation2) => - DoorSensorScreen(device: device))); + PowerClampPage(device: device))); case DeviceType.OneTouch: Navigator.push( diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index acf46f7..2bdc191 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -1081,5 +1081,10 @@ 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"; + + 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"; + //leakNormalIcon } 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/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: From ab661425687f04ede13de5ee7c494350804f19fa Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 16 Oct 2024 16:30:04 +0300 Subject: [PATCH 02/12] search --- .../widgets/power_clamp/power_clamp_page.dart | 266 ++++++++--------- .../devices/view/widgets/room_page.dart | 90 +++++- .../devices/view/widgets/wizard_page.dart | 273 ++++++++++-------- lib/generated/assets.dart | 2 + 4 files changed, 373 insertions(+), 258 deletions(-) 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 index 5fd0fc0..25dc6ee 100644 --- a/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_app/features/devices/bloc/door_sensor_bloc/door_sensor_bloc.dart'; import 'package:syncrow_app/features/devices/bloc/door_sensor_bloc/door_sensor_event.dart'; import 'package:syncrow_app/features/devices/bloc/door_sensor_bloc/door_sensor_state.dart'; @@ -61,139 +60,148 @@ class _PowerClampPageState extends State { onRefresh: () async { doorSensorBloc.add(const DoorSensorInitial()); }, - child: PageView.builder( - controller: _pageController, - onPageChanged: (int page) { - setState(() { - _currentPage = page; - }); - }, - itemBuilder: (context, index) { - return DefaultContainer( - child: Padding( - padding: const EdgeInsets.only( - left: 10, right: 10, top: 25, bottom: 20), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text('Energy usage'), - Row( - crossAxisAlignment: - CrossAxisAlignment.end, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - BodyLarge( - text: 'Total Energy \nConsumption', - fontSize: 20, - fontWeight: FontWeight.w700, - ), - Row( - children: [ - BodyLarge( - text: '8623.20 ', - fontSize: 20, - fontWeight: FontWeight.w700, - ), - BodySmall(text: 'kWh') - ], - ), - ], - ), - SizedBox( - height: 20, - ), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - PowerClampInfoCard( - iconPath: Assets.powerActiveIcon, - title: 'Active', - value: '700', - unit: ' w', - ), - PowerClampInfoCard( - iconPath: Assets.voltMeterIcon, - title: 'Current', - value: '3.06', - unit: ' A', - ), - PowerClampInfoCard( - iconPath: Assets.frequencyIcon, - title: 'Frequency', - value: '50', - unit: ' Hz', - ), - ], - ), - SizedBox( - height: 20, - ), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - BodyMedium( - text: 'Total consumption', - fontSize: 12, - fontWeight: FontWeight.w700, - ), - Text( - '10/08/2024', - style: TextStyle( + child: Padding( + padding: EdgeInsets.only(top: 25), + child: PageView.builder( + controller: _pageController, + onPageChanged: (int page) { + setState(() { + _currentPage = page; + }); + }, + itemBuilder: (context, index) { + return DefaultContainer( + child: Padding( + padding: const EdgeInsets.only( + left: 10, + right: 10, + top: 25, + bottom: 20), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text('Energy usage'), + const Row( + crossAxisAlignment: + CrossAxisAlignment.end, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + BodyLarge( + text: + 'Total Energy \nConsumption', + fontSize: 20, + fontWeight: FontWeight.w700, + ), + Row( + children: [ + BodyLarge( + text: '8623.20 ', + fontSize: 20, + fontWeight: FontWeight.w700, + ), + BodySmall(text: 'kWh') + ], + ), + ], + ), + SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + PowerClampInfoCard( + iconPath: Assets.powerActiveIcon, + title: 'Active', + value: '700', + unit: ' w', + ), + PowerClampInfoCard( + iconPath: Assets.voltMeterIcon, + title: 'Current', + value: '3.06', + unit: ' A', + ), + PowerClampInfoCard( + iconPath: Assets.frequencyIcon, + title: 'Frequency', + value: '50', + unit: ' Hz', + ), + ], + ), + SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + BodyMedium( + text: 'Total consumption', + fontSize: 12, + fontWeight: FontWeight.w700, + ), + Text( + '10/08/2024', + style: TextStyle( + fontSize: 8, + fontWeight: + FontWeight.w400), + ), + ], + ), + Row( + children: [ + BodyMedium( + text: '1000.00 ', + fontSize: 12, + fontWeight: + FontWeight.w700), + BodyMedium( + text: 'kWh', fontSize: 8, fontWeight: - FontWeight.w400), - ), - ], - ), - Row( - children: [ - BodyMedium( - text: '1000.00 ', - fontSize: 12, - fontWeight: FontWeight.w700), - BodyMedium( - text: 'kWh', - fontSize: 8, - fontWeight: FontWeight.w700), - ], - ), - ], - ), - SizedBox( - height: 310, - child: EnergyConsumptionPage( - 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), + FontWeight.w700), + ], + ), ], - totalConsumption: 100.0, - date: '10/08/2024', ), - ), - ], + SizedBox( + height: 310, + child: EnergyConsumptionPage( + 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: 100.0, + date: '10/08/2024', + ), + ), + ], + ), ), - ), - ); - }, - itemCount: _pageCount, + ); + }, + itemCount: _pageCount, + ), ), ), ), 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/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 2bdc191..3794f24 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -1085,6 +1085,8 @@ class Assets { 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"; + //leakNormalIcon } From 82ddc499acaf6c989fcd35d478a55836bde83a0c Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 16 Oct 2024 16:37:33 +0300 Subject: [PATCH 03/12] power_chart_changes --- .../view/widgets/power_clamp/power_chart.dart | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/lib/features/devices/view/widgets/power_clamp/power_chart.dart b/lib/features/devices/view/widgets/power_clamp/power_chart.dart index 673ef1a..d060cdd 100644 --- a/lib/features/devices/view/widgets/power_clamp/power_chart.dart +++ b/lib/features/devices/view/widgets/power_clamp/power_chart.dart @@ -22,7 +22,6 @@ class _EnergyConsumptionPageState extends State { @override void initState() { - // Use the provided data _chartData = widget.chartData; super.initState(); } @@ -65,17 +64,9 @@ class _EnergyConsumptionPageState extends State { bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: false, - getTitlesWidget: (value, meta) { - int index = value.toInt(); - if (index >= 0 && index < _chartData.length) { - return Text(_chartData[index].time, - style: TextStyle(fontSize: 10)); - } - return const SizedBox.shrink(); - }, ), ), - leftTitles: AxisTitles( + leftTitles: const AxisTitles( sideTitles: SideTitles( showTitles: false, ), @@ -83,11 +74,6 @@ class _EnergyConsumptionPageState extends State { rightTitles: AxisTitles( sideTitles: SideTitles( showTitles: false, - reservedSize: 40, - getTitlesWidget: (value, meta) { - return Text(value.toStringAsFixed(1) + ' kWh', - style: TextStyle(fontSize: 10)); - }, ), ), topTitles: AxisTitles( @@ -118,8 +104,8 @@ class _EnergyConsumptionPageState extends State { verticalInterval: 1, getDrawingVerticalLine: (value) { return FlLine( - color: Colors.blue.withOpacity(0.2), - dashArray: [10, 10], + color: Colors.grey.withOpacity(0.2), + dashArray: [8, 8], strokeWidth: 1, ); }, @@ -134,7 +120,6 @@ class _EnergyConsumptionPageState extends State { ), lineBarsData: [ LineChartBarData( - curveSmoothness: 0.5, preventCurveOverShooting: true, aboveBarData: BarAreaData(), @@ -164,7 +149,6 @@ class _EnergyConsumptionPageState extends State { ), isStrokeCapRound: true, barWidth: 6, - ), ], borderData: FlBorderData( From aa27ecf908ba9436a9b76153f023e07937828c6b Mon Sep 17 00:00:00 2001 From: mohammad Date: Thu, 17 Oct 2024 16:04:11 +0300 Subject: [PATCH 04/12] power clamp --- .../power_clamp_bloc/power_clamp_bloc.dart | 216 ++++++++++++ .../power_clamp_bloc/power_clamp_event.dart | 109 ++++++ .../power_clamp_bloc/power_clamp_state.dart | 48 +++ .../devices/model/power_clamp_model.dart | 28 ++ .../view/widgets/power_clamp/power_chart.dart | 3 +- .../widgets/power_clamp/power_clamp_page.dart | 320 +++++++++++------- .../view/widgets/room_page_switch.dart | 15 +- lib/utils/resource_manager/constants.dart | 40 ++- 8 files changed, 640 insertions(+), 139 deletions(-) create mode 100644 lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart create mode 100644 lib/features/devices/bloc/power_clamp_bloc/power_clamp_event.dart create mode 100644 lib/features/devices/bloc/power_clamp_bloc/power_clamp_state.dart create mode 100644 lib/features/devices/model/power_clamp_model.dart 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..b942a0a --- /dev/null +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart @@ -0,0 +1,216 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:firebase_database/firebase_database.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/model/status_model.dart'; +import 'package:syncrow_app/features/devices/view/widgets/power_clamp/power_chart.dart'; +import 'package:syncrow_app/services/api/devices_api.dart'; +import 'package:syncrow_app/utils/helpers/snack_bar.dart'; + +class PowerClampBloc extends Bloc { + final String PCId; + PowerClampBloc({ + required this.PCId, + }) : super(const PowerClampState()) { + on(_fetchStatus); + on(fetchLogsForLastMonth); + on(_mapReportToEnergyData); + on(selectTimeOfLinePassword); + } + //SelectDateEvent + Timer? _timer; + 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(doorContactState: false, batteryPercentage: 0); + + 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; + } + } + + DeviceReport recordGroups = + DeviceReport(startTime: '0', endTime: '0', data: []); + + 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; + emit(UpdateState(powerClampModel: deviceStatus)); + add(FetchEnergyData()); // Trigger mapping to EnergyData + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + } + } + + _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( + 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; // Divide by 1000 to remove milliseconds + + + DateTime dateTime = + selectedDateTime; // Assuming this is your DateTime object + formattedDate = DateFormat('yyyy/MM/dd').format(dateTime); + emit(DateSelectedState()); + } + } + + 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: Icon(Icons.arrow_left), + onPressed: () { + setState(() { + switchView(-1); + }); + }, + ), + Text( + views[currentIndex], + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500), + ), + IconButton( + icon: Icon(Icons.arrow_right), + onPressed: () { + setState(() { + switchView(1); + }); + }, + ), + ], + ); + }, + ); + } +} 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..ed2cbf5 --- /dev/null +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_event.dart @@ -0,0 +1,109 @@ +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 {} 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..5829bed --- /dev/null +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_state.dart @@ -0,0 +1,48 @@ +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}); +} 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..ca8f26f --- /dev/null +++ b/lib/features/devices/model/power_clamp_model.dart @@ -0,0 +1,28 @@ +import 'package:syncrow_app/features/devices/model/status_model.dart'; + +class PowerClampModel { + dynamic doorContactState; + dynamic batteryPercentage; + + PowerClampModel({ + required this.doorContactState, + required this.batteryPercentage, + }); + + factory PowerClampModel.fromJson(List jsonList) { + late dynamic _doorContactState; + late dynamic _batteryPercentage; + + for (int i = 0; i < jsonList.length; i++) { + if (jsonList[i].code == 'VoltageA') { + _doorContactState = jsonList[i].value ?? false; + } else if (jsonList[i].code == 'CurrentA') { + _batteryPercentage = jsonList[i].value ?? 0; + } + } + return PowerClampModel( + doorContactState: _doorContactState, + batteryPercentage: _batteryPercentage, + ); + } +} diff --git a/lib/features/devices/view/widgets/power_clamp/power_chart.dart b/lib/features/devices/view/widgets/power_clamp/power_chart.dart index d060cdd..575337e 100644 --- a/lib/features/devices/view/widgets/power_clamp/power_chart.dart +++ b/lib/features/devices/view/widgets/power_clamp/power_chart.dart @@ -120,6 +120,7 @@ class _EnergyConsumptionPageState extends State { ), lineBarsData: [ LineChartBarData( + preventCurveOvershootingThreshold: 0.1, curveSmoothness: 0.5, preventCurveOverShooting: true, aboveBarData: BarAreaData(), @@ -148,7 +149,7 @@ class _EnergyConsumptionPageState extends State { show: false, ), isStrokeCapRound: true, - barWidth: 6, + barWidth: 2, ), ], borderData: FlBorderData( 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 index 25dc6ee..b846af2 100644 --- a/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_app/features/devices/bloc/door_sensor_bloc/door_sensor_bloc.dart'; -import 'package:syncrow_app/features/devices/bloc/door_sensor_bloc/door_sensor_event.dart'; -import 'package:syncrow_app/features/devices/bloc/door_sensor_bloc/door_sensor_state.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/door_sensor_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_info_card.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart'; @@ -34,19 +34,26 @@ class _PowerClampPageState extends State { return DefaultScaffold( title: 'Power Clamp', child: BlocProvider( - create: (context) => DoorSensorBloc(DSId: widget.device?.uuid ?? '') - ..add(const DoorSensorInitial()), - child: BlocBuilder( + create: (context) => PowerClampBloc(PCId: widget.device?.uuid ?? '') + ..add(const PowerClampInitial()) + ..add(ReportLogsInitial(code: 'VoltageA')), + child: BlocBuilder( builder: (context, state) { - final doorSensorBloc = BlocProvider.of(context); - DoorSensorModel model = - DoorSensorModel(batteryPercentage: 0, doorContactState: false); + final _blocProvider = BlocProvider.of(context); + PowerClampModel model = + PowerClampModel(batteryPercentage: 0, doorContactState: false); + List chartData = []; + if (state is LoadingNewSate) { - model = state.doorSensor; + model = state.powerClampModel; } else if (state is UpdateState) { - model = state.doorSensor; + model = state.powerClampModel; + } else if (state is EnergyDataState) { + chartData = state.energyData; + print(chartData); } - return state is DoorSensorLoadingState + + return state is PowerClampLoadingState ? const Center( child: DefaultContainer( width: 50, @@ -58,7 +65,7 @@ class _PowerClampPageState extends State { Expanded( child: RefreshIndicator( onRefresh: () async { - doorSensorBloc.add(const DoorSensorInitial()); + _blocProvider.add(const PowerClampInitial()); }, child: Padding( padding: EdgeInsets.only(top: 25), @@ -68,6 +75,8 @@ class _PowerClampPageState extends State { setState(() { _currentPage = page; }); + _blocProvider.add( + const ReportLogsInitial(code: 'VoltageA')); }, itemBuilder: (context, index) { return DefaultContainer( @@ -78,125 +87,178 @@ class _PowerClampPageState extends State { top: 25, bottom: 20), child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text('Energy usage'), - const Row( - crossAxisAlignment: - CrossAxisAlignment.end, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - BodyLarge( - text: - 'Total Energy \nConsumption', - fontSize: 20, - fontWeight: FontWeight.w700, - ), - Row( - children: [ - BodyLarge( - text: '8623.20 ', - fontSize: 20, - fontWeight: FontWeight.w700, - ), - BodySmall(text: 'kWh') - ], - ), - ], - ), - SizedBox( - height: 20, - ), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - PowerClampInfoCard( - iconPath: Assets.powerActiveIcon, - title: 'Active', - value: '700', - unit: ' w', - ), - PowerClampInfoCard( - iconPath: Assets.voltMeterIcon, - title: 'Current', - value: '3.06', - unit: ' A', - ), - PowerClampInfoCard( - iconPath: Assets.frequencyIcon, - title: 'Frequency', - value: '50', - unit: ' Hz', - ), - ], - ), - SizedBox( - height: 20, - ), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - BodyMedium( - text: 'Total consumption', - fontSize: 12, - fontWeight: FontWeight.w700, - ), - Text( - '10/08/2024', - style: TextStyle( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text('Energy usage'), + const Row( + crossAxisAlignment: + CrossAxisAlignment.end, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + BodyLarge( + text: + 'Total Energy \nConsumption', + fontSize: 20, + fontWeight: FontWeight.w700, + ), + Row( + children: [ + BodyLarge( + text: '8623.20 ', + fontSize: 20, + fontWeight: FontWeight.w700, + ), + BodySmall(text: 'kWh') + ], + ), + ], + ), + const SizedBox( + height: 20, + ), + const Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + PowerClampInfoCard( + iconPath: + Assets.powerActiveIcon, + title: 'Active', + value: '700', + unit: ' w', + ), + PowerClampInfoCard( + iconPath: Assets.voltMeterIcon, + title: 'Current', + value: '3.06', + unit: ' A', + ), + PowerClampInfoCard( + iconPath: Assets.frequencyIcon, + title: 'Frequency', + value: '50', + unit: ' Hz', + ), + ], + ), + SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + BodyMedium( + text: 'Total consumption', + fontSize: 12, + fontWeight: FontWeight.w700, + ), + Text( + '10/08/2024', + style: TextStyle( + fontSize: 8, + fontWeight: + FontWeight.w400), + ), + ], + ), + Row( + children: [ + BodyMedium( + text: '1000.00 ', + fontSize: 12, + fontWeight: + FontWeight.w700), + BodyMedium( + text: 'kWh', fontSize: 8, fontWeight: - FontWeight.w400), - ), - ], - ), - Row( - children: [ - BodyMedium( - text: '1000.00 ', - fontSize: 12, - fontWeight: - FontWeight.w700), - BodyMedium( - text: 'kWh', - fontSize: 8, - fontWeight: - FontWeight.w700), - ], - ), - ], - ), - SizedBox( - height: 310, - child: EnergyConsumptionPage( - 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), + FontWeight.w700), + ], + ), ], - totalConsumption: 100.0, - date: '10/08/2024', ), - ), - ], - ), + SizedBox( + height: 290, + 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', + ), + ), + SizedBox( + height: 5, + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + DefaultContainer( + padding: EdgeInsets.all(0), + color: ColorsManager.grayBox, + child: SizedBox( + child: _blocProvider + .dateSwitcher(), + )), + InkWell( + onTap: () { + _blocProvider.add( + SelectDateEvent( + context: context)); + }, + child: DefaultContainer( + color: + ColorsManager.grayBox, + child: SizedBox( + child: Padding( + padding: + const EdgeInsets + .all(5), + child: Text( + _blocProvider + .formattedDate), + ), + )), + ), + ], + ) + ]), ), ); }, diff --git a/lib/features/devices/view/widgets/room_page_switch.dart b/lib/features/devices/view/widgets/room_page_switch.dart index bbe4bed..15b86eb 100644 --- a/lib/features/devices/view/widgets/room_page_switch.dart +++ b/lib/features/devices/view/widgets/room_page_switch.dart @@ -164,15 +164,14 @@ void showDeviceInterface(DeviceModel device, BuildContext context) { PageRouteBuilder( pageBuilder: (context, animation1, animation2) => WaterHeaterPage(device: device))); - // case DeviceType.DS: - // Navigator.push( - // context, - // PageRouteBuilder( - // pageBuilder: (context, animation1, animation2) => - // DoorSensorScreen(device: device))); + case DeviceType.DS: + Navigator.push( + context, + PageRouteBuilder( + pageBuilder: (context, animation1, animation2) => + DoorSensorScreen(device: device))); - - case DeviceType.DS: + case DeviceType.PC: Navigator.push( context, PageRouteBuilder( 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 } From b7b326969ac7d460dd940d2835d319a7b0c1f311 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 20 Oct 2024 16:57:13 +0300 Subject: [PATCH 05/12] power_clamp --- assets/icons/speedo_meter.svg | 16 + assets/icons/voltage_icon.svg | 41 ++ .../power_clamp_bloc/power_clamp_bloc.dart | 128 ++-- .../devices/model/power_clamp_model.dart | 96 ++- .../widgets/power_clamp/power_clamp_page.dart | 632 ++++++++++++------ .../widgets/power_clamp/power_info_card.dart | 9 +- lib/generated/assets.dart | 5 +- lib/services/api/api_links_endpoints.dart | 4 +- lib/services/api/devices_api.dart | 12 + 9 files changed, 658 insertions(+), 285 deletions(-) create mode 100644 assets/icons/speedo_meter.svg create mode 100644 assets/icons/voltage_icon.svg 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/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/power_clamp_bloc/power_clamp_bloc.dart b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart index b942a0a..0ab1f11 100644 --- a/lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:dio/dio.dart'; -import 'package:firebase_database/firebase_database.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; @@ -9,17 +8,15 @@ import 'package:syncrow_app/features/devices/bloc/power_clamp_bloc/power_clamp_e 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/model/status_model.dart'; import 'package:syncrow_app/features/devices/view/widgets/power_clamp/power_chart.dart'; import 'package:syncrow_app/services/api/devices_api.dart'; -import 'package:syncrow_app/utils/helpers/snack_bar.dart'; class PowerClampBloc extends Bloc { final String PCId; PowerClampBloc({ required this.PCId, }) : super(const PowerClampState()) { - on(_fetchStatus); + on(_fetchPowerClampInfo); on(fetchLogsForLastMonth); on(_mapReportToEnergyData); on(selectTimeOfLinePassword); @@ -32,30 +29,80 @@ class PowerClampBloc extends Bloc { bool lowBattery = false; bool closingReminder = false; bool doorAlarm = false; - PowerClampModel deviceStatus = - PowerClampModel(doorContactState: false, batteryPercentage: 0); - void _fetchStatus( + 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 _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; + // } + // } + + void _fetchPowerClampInfo( 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, - ); + var response = await DevicesAPI.getPowerClampStatus(PCId); + PowerClampModel deviceStatus = PowerClampModel.fromJson(response); emit(UpdateState(powerClampModel: deviceStatus)); Future.delayed(const Duration(milliseconds: 500)); - // _listenToChanges(); } catch (e) { emit(PowerClampFailedState(errorMessage: e.toString())); return; } } + //List statusModelList = []; + // for (var status in response['status']) { + // statusModelList.add(StatusModel.fromJson(status)); + // } + // deviceStatus = PowerClampModel.fromJson( + // statusModelList, + // ); + DeviceReport recordGroups = DeviceReport(startTime: '0', endTime: '0', data: []); @@ -84,33 +131,33 @@ class PowerClampBloc extends Bloc { } } - _listenToChanges() { - try { - DatabaseReference ref = - FirebaseDatabase.instance.ref('device-status/$PCId'); - Stream stream = ref.onValue; + // _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 = []; + // 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)); - }); + // usersMap['status'].forEach((element) { + // statusList.add(StatusModel(code: element['code'], value: true)); + // }); - deviceStatus = PowerClampModel.fromJson(statusList); - if (!isClosed) { - add( - PowerClampSwitch(switchD: deviceStatus.doorContactState), - ); - } - }); - } catch (_) {} - } + // 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( @@ -166,7 +213,6 @@ class PowerClampBloc extends Bloc { selectedDateTime.minute, ).millisecondsSinceEpoch ~/ 1000; // Divide by 1000 to remove milliseconds - DateTime dateTime = selectedDateTime; // Assuming this is your DateTime object diff --git a/lib/features/devices/model/power_clamp_model.dart b/lib/features/devices/model/power_clamp_model.dart index ca8f26f..7fb09d4 100644 --- a/lib/features/devices/model/power_clamp_model.dart +++ b/lib/features/devices/model/power_clamp_model.dart @@ -1,28 +1,86 @@ -import 'package:syncrow_app/features/devices/model/status_model.dart'; - +// PowerClampModel class to represent the response class PowerClampModel { - dynamic doorContactState; - dynamic batteryPercentage; + String productUuid; + String productType; + PowerStatus status; PowerClampModel({ - required this.doorContactState, - required this.batteryPercentage, + required this.productUuid, + required this.productType, + required this.status, }); - factory PowerClampModel.fromJson(List jsonList) { - late dynamic _doorContactState; - late dynamic _batteryPercentage; - - for (int i = 0; i < jsonList.length; i++) { - if (jsonList[i].code == 'VoltageA') { - _doorContactState = jsonList[i].value ?? false; - } else if (jsonList[i].code == 'CurrentA') { - _batteryPercentage = jsonList[i].value ?? 0; - } - } + factory PowerClampModel.fromJson(Map json) { return PowerClampModel( - doorContactState: _doorContactState, - batteryPercentage: _batteryPercentage, + 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_clamp_page.dart b/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart index b846af2..95ff80f 100644 --- a/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart @@ -27,7 +27,26 @@ class PowerClampPage extends StatefulWidget { class _PowerClampPageState extends State { final PageController _pageController = PageController(); int _currentPage = 0; - final int _pageCount = 5; + final int _pageCount = 4; + @override + void initState() { + super.initState(); + // Add listener to update _currentPage when the page is changed + _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) { @@ -35,24 +54,68 @@ class _PowerClampPageState extends State { title: 'Power Clamp', child: BlocProvider( create: (context) => PowerClampBloc(PCId: widget.device?.uuid ?? '') - ..add(const PowerClampInitial()) - ..add(ReportLogsInitial(code: 'VoltageA')), + ..add(const PowerClampInitial()), child: BlocBuilder( builder: (context, state) { final _blocProvider = BlocProvider.of(context); - PowerClampModel model = - PowerClampModel(batteryPercentage: 0, doorContactState: false); + 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 LoadingNewSate) { - model = state.powerClampModel; - } else if (state is UpdateState) { + if (state is UpdateState) { model = state.powerClampModel; } else if (state is EnergyDataState) { chartData = state.energyData; print(chartData); } - return state is PowerClampLoadingState ? const Center( child: DefaultContainer( @@ -62,209 +125,147 @@ class _PowerClampPageState extends State { ) : Column( children: [ - Expanded( + Flexible( child: RefreshIndicator( onRefresh: () async { _blocProvider.add(const PowerClampInitial()); }, - child: Padding( - padding: EdgeInsets.only(top: 25), - child: PageView.builder( - controller: _pageController, - onPageChanged: (int page) { - setState(() { - _currentPage = page; - }); - _blocProvider.add( - const ReportLogsInitial(code: 'VoltageA')); - }, - itemBuilder: (context, index) { - return DefaultContainer( - child: Padding( - padding: const EdgeInsets.only( - left: 10, - right: 10, - top: 25, - bottom: 20), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text('Energy usage'), - const Row( - crossAxisAlignment: - CrossAxisAlignment.end, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - BodyLarge( - text: - 'Total Energy \nConsumption', - fontSize: 20, - fontWeight: FontWeight.w700, - ), - Row( - children: [ - BodyLarge( - text: '8623.20 ', - fontSize: 20, - fontWeight: FontWeight.w700, - ), - BodySmall(text: 'kWh') - ], - ), - ], - ), - const SizedBox( - height: 20, - ), - const Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - PowerClampInfoCard( - iconPath: - Assets.powerActiveIcon, - title: 'Active', - value: '700', - unit: ' w', - ), - PowerClampInfoCard( - iconPath: Assets.voltMeterIcon, - title: 'Current', - value: '3.06', - unit: ' A', - ), - PowerClampInfoCard( - iconPath: Assets.frequencyIcon, - title: 'Frequency', - value: '50', - unit: ' Hz', - ), - ], - ), - SizedBox( - height: 20, - ), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - BodyMedium( - text: 'Total consumption', - fontSize: 12, - fontWeight: FontWeight.w700, - ), - Text( - '10/08/2024', - style: TextStyle( - fontSize: 8, - fontWeight: - FontWeight.w400), - ), - ], - ), - Row( - children: [ - BodyMedium( - text: '1000.00 ', - fontSize: 12, - fontWeight: - FontWeight.w700), - BodyMedium( - text: 'kWh', - fontSize: 8, - fontWeight: - FontWeight.w700), - ], - ), - ], - ), - SizedBox( - height: 290, - 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', - ), - ), - SizedBox( - height: 5, - ), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - DefaultContainer( - padding: EdgeInsets.all(0), - color: ColorsManager.grayBox, - child: SizedBox( - child: _blocProvider - .dateSwitcher(), - )), - InkWell( - onTap: () { - _blocProvider.add( - SelectDateEvent( - context: context)); - }, - child: DefaultContainer( - color: - ColorsManager.grayBox, - child: SizedBox( - child: Padding( - padding: - const EdgeInsets - .all(5), - child: Text( - _blocProvider - .formattedDate), - ), - )), - ), - ], - ) - ]), - ), - ); - }, - itemCount: _pageCount, - ), - ), + child: + PageView(controller: _pageController, children: [ + powerClampCard( + title: 'Total Energy \nConsumption', + isGeneral: true, + dateSwitcher: _blocProvider.dateSwitcher(), + formattedDate: _blocProvider.formattedDate, + selectDateEvent: () { + _blocProvider + .add(SelectDateEvent(context: context)); + }, + totalFrequencyGeneral: model + .status.general.dataPoints.isNotEmpty + ? model.status.general.dataPoints[0].value + .toString() + : 'N/A', + totalActiveGeneral: + model.status.general.dataPoints.length > 3 + ? model + .status.general.dataPoints[3].value + .toString() + : 'N/A', + totalCurrentGeneral: + model.status.general.dataPoints.length > 2 + ? model + .status.general.dataPoints[2].value + .toString() + : 'N/A', + totalVoltage: + model.status.general.dataPoints.length > 1 + ? model + .status.general.dataPoints[1].value + .toString() + : 'N/A', + chartData: chartData, + context: context), + powerClampCard( + title: 'Phase A Energy \nConsumption', + dateSwitcher: _blocProvider.dateSwitcher(), + formattedDate: _blocProvider.formattedDate, + selectDateEvent: () { + _blocProvider + .add(SelectDateEvent(context: context)); + }, + totalFactor: model + .status.phaseA.dataPoints.isNotEmpty + ? model.status.phaseA.dataPoints[0].value + .toString() + : 'N/A', + totalActive: + model.status.phaseA.dataPoints.length > 3 + ? model + .status.phaseA.dataPoints[3].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( + title: 'Phase B Energy \nConsumption', + dateSwitcher: _blocProvider.dateSwitcher(), + formattedDate: _blocProvider.formattedDate, + selectDateEvent: () { + _blocProvider + .add(SelectDateEvent(context: context)); + }, + totalFactor: model + .status.phaseB.dataPoints.isNotEmpty + ? model.status.phaseB.dataPoints[0].value + .toString() + : 'N/A', + totalActive: + model.status.phaseB.dataPoints.length > 3 + ? model + .status.phaseB.dataPoints[3].value + .toString() + : 'N/A', + totalCurrent: + model.status.phaseB.dataPoints.length > 2 + ? model + .status.phaseB.dataPoints[2].value + .toString() + : 'N/A', + totalVoltage: + model.status.phaseB.dataPoints.length > 1 + ? model + .status.phaseB.dataPoints[1].value + .toString() + : 'N/A', + chartData: chartData, + context: context), + powerClampCard( + title: 'Phase C Energy \nConsumption', + dateSwitcher: _blocProvider.dateSwitcher(), + formattedDate: _blocProvider.formattedDate, + selectDateEvent: () { + _blocProvider + .add(SelectDateEvent(context: context)); + }, + totalFactor: model + .status.phaseC.dataPoints.isNotEmpty + ? model.status.phaseC.dataPoints[0].value + .toString() + : 'N/A', + totalActive: + model.status.phaseC.dataPoints.length > 3 + ? model + .status.phaseC.dataPoints[3].value + .toString() + : 'N/A', + totalCurrent: + model.status.phaseC.dataPoints.length > 2 + ? model + .status.phaseC.dataPoints[2].value + .toString() + : 'N/A', + totalVoltage: + model.status.phaseC.dataPoints.length > 1 + ? model + .status.phaseC.dataPoints[1].value + .toString() + : 'N/A', + chartData: chartData, + context: context), + ]), ), ), Padding( @@ -277,20 +278,16 @@ class _PowerClampPageState extends State { margin: const EdgeInsets.symmetric(horizontal: 4.0), height: 10.0, - width: _currentPage == index ? 10.0 : 10.0, + width: _currentPage == index + ? 20.0 + : 10.0, // Change width for current page decoration: BoxDecoration( color: _currentPage == index - ? Colors.grey + ? Colors + .grey // Use a different color for the active indicator : ColorsManager.greyColor, borderRadius: BorderRadius.circular(5.0), ), - child: Center( - child: _currentPage == index - ? Text( - '', - ) - : SizedBox.shrink(), - ), ); }), ), @@ -302,4 +299,205 @@ class _PowerClampPageState extends State { ), ); } + + DefaultContainer powerClampCard( + {bool? isGeneral, + String? title, + String? totalCurrent, + String? totalActiveGeneral, + String? totalCurrentGeneral, + String? totalFrequencyGeneral, + String? totalVoltage, + String? totalActive, + String? totalFrequency, + String? totalFactor, + Widget? dateSwitcher, + String? formattedDate, + Function()? selectDateEvent, + List? chartData, + 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: [ + Text('Energy usage'), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BodyLarge( + text: title!, + // 'Total Energy \nConsumption', + fontSize: 20, + fontWeight: FontWeight.w700, + ), + Row( + children: [ + BodyLarge( + text: totalVoltage!, + fontSize: 20, + fontWeight: FontWeight.w700, + ), + BodySmall(text: 'kWh') + ], + ), + ], + ), + const SizedBox( + height: 20, + ), + 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: '', + ), + ], + ) + ], + ), + SizedBox( + height: 10, + ), + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BodyMedium( + text: 'Total consumption', + fontSize: 12, + fontWeight: FontWeight.w700, + ), + Text( + '10/08/2024', + style: + TextStyle(fontSize: 8, fontWeight: FontWeight.w400), + ), + ], + ), + Row( + children: [ + BodyMedium( + text: '1000.00 ', + fontSize: 12, + fontWeight: FontWeight.w700), + BodyMedium( + text: 'kWh', + fontSize: 8, + fontWeight: FontWeight.w700), + ], + ), + ], + ), + SizedBox( + height: 290, + 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', + ), + ), + 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_info_card.dart b/lib/features/devices/view/widgets/power_clamp/power_info_card.dart index a79adb0..04d21e6 100644 --- a/lib/features/devices/view/widgets/power_clamp/power_info_card.dart +++ b/lib/features/devices/view/widgets/power_clamp/power_info_card.dart @@ -22,13 +22,13 @@ class PowerClampInfoCard extends StatelessWidget { Widget build(BuildContext context) { return Expanded( child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(5.0), child: DefaultContainer( - height: 60, + height: 55, color: ColorsManager.grayBox, child: Row( crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: SvgPicture.asset( @@ -40,6 +40,7 @@ class PowerClampInfoCard extends StatelessWidget { flex: 3, child: Column( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ BodyMedium( fontWeight: FontWeight.w400, @@ -47,7 +48,7 @@ class PowerClampInfoCard extends StatelessWidget { text: title, ), Row( - mainAxisAlignment: MainAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ BodyMedium( diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index 3794f24..1b0c77e 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -1086,7 +1086,8 @@ class Assets { 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"; - - //leakNormalIcon + //speedo_meter } 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..bb58b42 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, From 5b1fd3bdd87297a1c1ad6a6e1fd3124cd988eaa4 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 22 Oct 2024 10:10:57 +0300 Subject: [PATCH 06/12] power+clamp --- .../widgets/power_clamp/power_clamp_card.dart | 234 ++++++++++++++++++ .../widgets/power_clamp/power_clamp_page.dart | 6 +- 2 files changed, 236 insertions(+), 4 deletions(-) create mode 100644 lib/features/devices/view/widgets/power_clamp/power_clamp_card.dart 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..90e8cb0 --- /dev/null +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_card.dart @@ -0,0 +1,234 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/devices/model/power_clamp_model.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? totalFactor; + final Widget? dateSwitcher; + final String? formattedDate; + final Function()? selectDateEvent; + final List? chartData; + final BuildContext? context; + + const PowerClampCard({ + Key? key, + this.isGeneral, + this.title, + this.totalCurrent, + this.totalActiveGeneral, + this.totalCurrentGeneral, + this.totalFrequencyGeneral, + this.totalVoltage, + this.totalActive, + this.totalFrequency, + this.totalFactor, + this.dateSwitcher, + this.formattedDate, + this.selectDateEvent, + this.chartData, + this.context, + }) : 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: totalVoltage!, + fontSize: 20, + fontWeight: FontWeight.w700, + ), + const BodySmall(text: 'kWh') + ], + ), + ], + ), + const SizedBox( + height: 20, + ), + 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: '', + ), + ], + ) + ], + ), + const SizedBox( + height: 10, + ), + const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BodyMedium( + text: 'Total consumption', + fontSize: 12, + fontWeight: FontWeight.w700, + ), + Text( + '10/08/2024', + style: + TextStyle(fontSize: 8, fontWeight: FontWeight.w400), + ), + ], + ), + Row( + children: [ + BodyMedium( + text: '1000.00 ', + fontSize: 12, + fontWeight: FontWeight.w700), + BodyMedium( + text: 'kWh', + fontSize: 8, + fontWeight: FontWeight.w700), + ], + ), + ], + ), + SizedBox( + height: 290, + 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 index 95ff80f..4b30614 100644 --- a/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart @@ -114,7 +114,6 @@ class _PowerClampPageState extends State { model = state.powerClampModel; } else if (state is EnergyDataState) { chartData = state.energyData; - print(chartData); } return state is PowerClampLoadingState ? const Center( @@ -171,8 +170,7 @@ class _PowerClampPageState extends State { dateSwitcher: _blocProvider.dateSwitcher(), formattedDate: _blocProvider.formattedDate, selectDateEvent: () { - _blocProvider - .add(SelectDateEvent(context: context)); + _blocProvider.add(SelectDateEvent(context: context)); }, totalFactor: model .status.phaseA.dataPoints.isNotEmpty @@ -300,7 +298,7 @@ class _PowerClampPageState extends State { ); } - DefaultContainer powerClampCard( + DefaultContainer powerClampCard( {bool? isGeneral, String? title, String? totalCurrent, From 9e678fddafef8a2ab65c0fc8e681c2e73ffa9a61 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 22 Oct 2024 10:13:12 +0300 Subject: [PATCH 07/12] power_clamp --- .../bloc/power_clamp_bloc/power_clamp_bloc.dart | 10 ++++------ .../view/widgets/power_clamp/power_clamp_card.dart | 1 - 2 files changed, 4 insertions(+), 7 deletions(-) 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 index 0ab1f11..4f2bbdb 100644 --- a/lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart @@ -22,7 +22,6 @@ class PowerClampBloc extends Bloc { on(selectTimeOfLinePassword); } //SelectDateEvent - Timer? _timer; DateTime? dateTime = DateTime.now(); String formattedDate = DateFormat('yyyy/MM/dd').format(DateTime.now()); @@ -212,10 +211,9 @@ class PowerClampBloc extends Bloc { selectedDateTime.hour, selectedDateTime.minute, ).millisecondsSinceEpoch ~/ - 1000; // Divide by 1000 to remove milliseconds + 1000; - DateTime dateTime = - selectedDateTime; // Assuming this is your DateTime object + DateTime dateTime = selectedDateTime; formattedDate = DateFormat('yyyy/MM/dd').format(dateTime); emit(DateSelectedState()); } @@ -244,10 +242,10 @@ class PowerClampBloc extends Bloc { ), Text( views[currentIndex], - style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500), + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), ), IconButton( - icon: Icon(Icons.arrow_right), + icon: const Icon(Icons.arrow_right), onPressed: () { setState(() { switchView(1); 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 index 90e8cb0..f547113 100644 --- a/lib/features/devices/view/widgets/power_clamp/power_clamp_card.dart +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_card.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_app/features/devices/model/power_clamp_model.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'; From 91ba2bad78568a88686fe14fd5e14d1536396ee0 Mon Sep 17 00:00:00 2001 From: mohammad Date: Tue, 22 Oct 2024 13:21:15 +0300 Subject: [PATCH 08/12] power_clamp --- .../power_clamp_bloc/power_clamp_bloc.dart | 3 +- .../widgets/power_clamp/power_clamp_card.dart | 6 +- .../widgets/power_clamp/power_clamp_page.dart | 597 +++++------------- .../widgets/power_clamp/power_clamp_test.dart | 312 +++++++++ 4 files changed, 473 insertions(+), 445 deletions(-) create mode 100644 lib/features/devices/view/widgets/power_clamp/power_clamp_test.dart 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 index 4f2bbdb..5bec5cf 100644 --- a/lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart @@ -17,7 +17,7 @@ class PowerClampBloc extends Bloc { required this.PCId, }) : super(const PowerClampState()) { on(_fetchPowerClampInfo); - on(fetchLogsForLastMonth); + // on(fetchLogsForLastMonth); on(_mapReportToEnergyData); on(selectTimeOfLinePassword); } @@ -87,7 +87,6 @@ class PowerClampBloc extends Bloc { var response = await DevicesAPI.getPowerClampStatus(PCId); PowerClampModel deviceStatus = PowerClampModel.fromJson(response); emit(UpdateState(powerClampModel: deviceStatus)); - Future.delayed(const Duration(milliseconds: 500)); } catch (e) { emit(PowerClampFailedState(errorMessage: e.toString())); return; 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 index f547113..372bb3d 100644 --- a/lib/features/devices/view/widgets/power_clamp/power_clamp_card.dart +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_card.dart @@ -6,7 +6,6 @@ import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dar 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 { @@ -22,6 +21,7 @@ class PowerClampCard extends StatelessWidget { final String? totalFactor; final Widget? dateSwitcher; final String? formattedDate; + final String? energyConsumption; final Function()? selectDateEvent; final List? chartData; final BuildContext? context; @@ -43,6 +43,8 @@ class PowerClampCard extends StatelessWidget { this.selectDateEvent, this.chartData, this.context, + this. energyConsumption, + //nConsumption }) : super(key: key); @override @@ -68,7 +70,7 @@ class PowerClampCard extends StatelessWidget { Row( children: [ BodyLarge( - text: totalVoltage!, + text: energyConsumption!, fontSize: 20, fontWeight: FontWeight.w700, ), 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 index 4b30614..0e6a608 100644 --- a/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart @@ -6,13 +6,9 @@ import 'package:syncrow_app/features/devices/bloc/power_clamp_bloc/power_clamp_s 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_info_card.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/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'; class PowerClampPage extends StatefulWidget { @@ -21,29 +17,32 @@ class PowerClampPage extends StatefulWidget { const PowerClampPage({super.key, this.device}); @override - _PowerClampPageState createState() => _PowerClampPageState(); + State createState() => _PowerClampPageState(); } class _PowerClampPageState extends State { final PageController _pageController = PageController(); int _currentPage = 0; - final int _pageCount = 4; + static const int _pageCount = 4; + @override void initState() { super.initState(); - // Add listener to update _currentPage when the page is changed - _pageController.addListener(() { - int nextPage = _pageController.page?.round() ?? 0; - if (_currentPage != nextPage) { - setState(() { - _currentPage = nextPage; - }); - } - }); + _pageController.addListener(_handlePageChange); + } + + void _handlePageChange() { + int nextPage = _pageController.page?.round() ?? 0; + if (_currentPage != nextPage) { + setState(() { + _currentPage = nextPage; + }); + } } @override void dispose() { + _pageController.removeListener(_handlePageChange); _pageController.dispose(); super.dispose(); } @@ -57,57 +56,8 @@ class _PowerClampPageState extends State { ..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), - ], - ), - ), - ); + final blocProvider = context.read(); + PowerClampModel model = _initialPowerClampModel(); List chartData = []; if (state is UpdateState) { @@ -115,386 +65,151 @@ class _PowerClampPageState extends State { } 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( - title: 'Total Energy \nConsumption', - isGeneral: true, - dateSwitcher: _blocProvider.dateSwitcher(), - formattedDate: _blocProvider.formattedDate, - selectDateEvent: () { - _blocProvider - .add(SelectDateEvent(context: context)); - }, - totalFrequencyGeneral: model - .status.general.dataPoints.isNotEmpty - ? model.status.general.dataPoints[0].value - .toString() - : 'N/A', - totalActiveGeneral: - model.status.general.dataPoints.length > 3 - ? model - .status.general.dataPoints[3].value - .toString() - : 'N/A', - totalCurrentGeneral: - model.status.general.dataPoints.length > 2 - ? model - .status.general.dataPoints[2].value - .toString() - : 'N/A', - totalVoltage: - model.status.general.dataPoints.length > 1 - ? model - .status.general.dataPoints[1].value - .toString() - : 'N/A', - chartData: chartData, - context: context), - powerClampCard( - title: 'Phase A Energy \nConsumption', - dateSwitcher: _blocProvider.dateSwitcher(), - formattedDate: _blocProvider.formattedDate, - selectDateEvent: () { - _blocProvider.add(SelectDateEvent(context: context)); - }, - totalFactor: model - .status.phaseA.dataPoints.isNotEmpty - ? model.status.phaseA.dataPoints[0].value - .toString() - : 'N/A', - totalActive: - model.status.phaseA.dataPoints.length > 3 - ? model - .status.phaseA.dataPoints[3].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( - title: 'Phase B Energy \nConsumption', - dateSwitcher: _blocProvider.dateSwitcher(), - formattedDate: _blocProvider.formattedDate, - selectDateEvent: () { - _blocProvider - .add(SelectDateEvent(context: context)); - }, - totalFactor: model - .status.phaseB.dataPoints.isNotEmpty - ? model.status.phaseB.dataPoints[0].value - .toString() - : 'N/A', - totalActive: - model.status.phaseB.dataPoints.length > 3 - ? model - .status.phaseB.dataPoints[3].value - .toString() - : 'N/A', - totalCurrent: - model.status.phaseB.dataPoints.length > 2 - ? model - .status.phaseB.dataPoints[2].value - .toString() - : 'N/A', - totalVoltage: - model.status.phaseB.dataPoints.length > 1 - ? model - .status.phaseB.dataPoints[1].value - .toString() - : 'N/A', - chartData: chartData, - context: context), - powerClampCard( - title: 'Phase C Energy \nConsumption', - dateSwitcher: _blocProvider.dateSwitcher(), - formattedDate: _blocProvider.formattedDate, - selectDateEvent: () { - _blocProvider - .add(SelectDateEvent(context: context)); - }, - totalFactor: model - .status.phaseC.dataPoints.isNotEmpty - ? model.status.phaseC.dataPoints[0].value - .toString() - : 'N/A', - totalActive: - model.status.phaseC.dataPoints.length > 3 - ? model - .status.phaseC.dataPoints[3].value - .toString() - : 'N/A', - totalCurrent: - model.status.phaseC.dataPoints.length > 2 - ? model - .status.phaseC.dataPoints[2].value - .toString() - : 'N/A', - totalVoltage: - model.status.phaseC.dataPoints.length > 1 - ? model - .status.phaseC.dataPoints[1].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 - ? 20.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), - ), - ); - }), - ), - ), - ], - ); + + 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: + _buildPowerClampCards(model, chartData, blocProvider), + ), + ), + ), + _buildPageIndicator(), + ], + ); }, ), ), ); } - DefaultContainer powerClampCard( - {bool? isGeneral, - String? title, - String? totalCurrent, - String? totalActiveGeneral, - String? totalCurrentGeneral, - String? totalFrequencyGeneral, - String? totalVoltage, - String? totalActive, - String? totalFrequency, - String? totalFactor, - Widget? dateSwitcher, - String? formattedDate, - Function()? selectDateEvent, - List? chartData, - 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: [ - Text('Energy usage'), - Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - BodyLarge( - text: title!, - // 'Total Energy \nConsumption', - fontSize: 20, - fontWeight: FontWeight.w700, - ), - Row( - children: [ - BodyLarge( - text: totalVoltage!, - fontSize: 20, - fontWeight: FontWeight.w700, - ), - BodySmall(text: 'kWh') - ], - ), - ], - ), - const SizedBox( - height: 20, - ), - 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: '', - ), - ], - ) - ], - ), - SizedBox( - height: 10, - ), - const Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - BodyMedium( - text: 'Total consumption', - fontSize: 12, - fontWeight: FontWeight.w700, - ), - Text( - '10/08/2024', - style: - TextStyle(fontSize: 8, fontWeight: FontWeight.w400), - ), - ], - ), - Row( - children: [ - BodyMedium( - text: '1000.00 ', - fontSize: 12, - fontWeight: FontWeight.w700), - BodyMedium( - text: 'kWh', - fontSize: 8, - fontWeight: FontWeight.w700), - ], - ), - ], - ), - SizedBox( - height: 290, - 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', - ), - ), - 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!), - ), - )), - ), - ], - ) - ]), + 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( + 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..79f1e73 --- /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: _blocProvider.dateSwitcher(), + 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), + ), + ); + }), + ), + ), + ], + ); + }, + ), + ), + ); + } +} From 600ed7992d9b782dd9b9104a72e0f590abf78fad Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 23 Oct 2024 16:38:40 +0300 Subject: [PATCH 09/12] power_clamp_chart_functionality --- .../garage_door_bloc/garage_door_bloc.dart | 3 + .../power_clamp_bloc/power_clamp_bloc.dart | 827 ++++++++++++++---- .../power_clamp_bloc/power_clamp_event.dart | 10 + .../power_clamp_bloc/power_clamp_state.dart | 10 + .../devices/model/device_report_model.dart | 27 + .../widgets/power_clamp/power_clamp_card.dart | 68 +- .../widgets/power_clamp/power_clamp_page.dart | 40 +- .../widgets/power_clamp/power_clamp_test.dart | 594 ++++++------- lib/services/api/devices_api.dart | 1 + 9 files changed, 1095 insertions(+), 485 deletions(-) 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 index 5bec5cf..48224a1 100644 --- a/lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_bloc.dart @@ -1,6 +1,5 @@ import 'dart:async'; - -import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; @@ -18,12 +17,11 @@ class PowerClampBloc extends Bloc { }) : super(const PowerClampState()) { on(_fetchPowerClampInfo); // on(fetchLogsForLastMonth); - on(_mapReportToEnergyData); - on(selectTimeOfLinePassword); + // on(_mapReportToEnergyData); + on(checkDayMonthYearSelected); + on(_filterRecordsByDate); } - //SelectDateEvent DateTime? dateTime = DateTime.now(); - String formattedDate = DateFormat('yyyy/MM/dd').format(DateTime.now()); bool lowBattery = false; bool closingReminder = false; @@ -59,26 +57,6 @@ class PowerClampBloc extends Bloc { ), ), ); - // 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; - // } - // } void _fetchPowerClampInfo( PowerClampInitial event, Emitter emit) async { @@ -93,130 +71,191 @@ class PowerClampBloc extends Bloc { } } - //List statusModelList = []; - // for (var status in response['status']) { - // statusModelList.add(StatusModel.fromJson(status)); - // } - // deviceStatus = PowerClampModel.fromJson( - // statusModelList, - // ); - DeviceReport recordGroups = DeviceReport(startTime: '0', endTime: '0', data: []); - Future fetchLogsForLastMonth( - ReportLogsInitial event, Emitter emit) async { - DateTime now = DateTime.now(); + EventDevice recordGroupsDateTime = + EventDevice(code: '', eventTime: DateTime.now(), value: ''); - 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; - emit(UpdateState(powerClampModel: deviceStatus)); - add(FetchEnergyData()); // Trigger mapping to EnergyData - } on DioException catch (e) { - final errorData = e.response!.data; - String errorMessage = errorData['message']; - } - } - - // _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( - 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()); - } - } + 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']; @@ -232,7 +271,7 @@ class PowerClampBloc extends Bloc { mainAxisSize: MainAxisSize.min, children: [ IconButton( - icon: Icon(Icons.arrow_left), + icon: const Icon(Icons.arrow_left), onPressed: () { setState(() { switchView(-1); @@ -256,4 +295,498 @@ class PowerClampBloc extends Bloc { }, ); } + + 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 index ed2cbf5..fa038ce 100644 --- a/lib/features/devices/bloc/power_clamp_bloc/power_clamp_event.dart +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_event.dart @@ -107,3 +107,13 @@ class TickTimer extends PowerClampEvent { class StopTimer extends PowerClampEvent {} class OnClose extends PowerClampEvent {} + + +class FilterRecordsByDateEvent extends PowerClampEvent { + final DateTime selectedDate; + final String viewType; // 'Day', 'Month', 'Year' + + 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 index 5829bed..1fd6d51 100644 --- a/lib/features/devices/bloc/power_clamp_bloc/power_clamp_state.dart +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_state.dart @@ -1,4 +1,5 @@ import 'package:equatable/equatable.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'; @@ -12,7 +13,9 @@ class PowerClampState extends Equatable { class PowerClampInitialState extends PowerClampState {} class PowerClampLoadingState extends PowerClampState {} + class ChangeTimeState extends PowerClampState {} + class DateSelectedState extends PowerClampState {} //DateSelectedState @@ -46,3 +49,10 @@ class EnergyDataState extends PowerClampState { const EnergyDataState({required this.energyData}); } + +// State for filtered records +class FilterRecordsState extends PowerClampState { + final List filteredRecords; + + FilterRecordsState({required this.filteredRecords}); +} 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/view/widgets/power_clamp/power_clamp_card.dart b/lib/features/devices/view/widgets/power_clamp/power_clamp_card.dart index 372bb3d..5c758c8 100644 --- a/lib/features/devices/view/widgets/power_clamp/power_clamp_card.dart +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_card.dart @@ -18,10 +18,11 @@ class PowerClampCard extends StatelessWidget { 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 String? energyConsumption; final Function()? selectDateEvent; final List? chartData; final BuildContext? context; @@ -32,6 +33,7 @@ class PowerClampCard extends StatelessWidget { this.title, this.totalCurrent, this.totalActiveGeneral, + this.dateTimeSelected, this.totalCurrentGeneral, this.totalFrequencyGeneral, this.totalVoltage, @@ -43,7 +45,7 @@ class PowerClampCard extends StatelessWidget { this.selectDateEvent, this.chartData, this.context, - this. energyConsumption, + this.energyConsumption, //nConsumption }) : super(key: key); @@ -80,7 +82,7 @@ class PowerClampCard extends StatelessWidget { ], ), const SizedBox( - height: 20, + height: 10, ), isGeneral == true ? Row( @@ -144,28 +146,25 @@ class PowerClampCard extends StatelessWidget { ) ], ), - const SizedBox( - height: 10, - ), - const Row( + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - BodyMedium( + const BodyMedium( text: 'Total consumption', fontSize: 12, fontWeight: FontWeight.w700, ), Text( - '10/08/2024', - style: - TextStyle(fontSize: 8, fontWeight: FontWeight.w400), + dateTimeSelected !, + style: const TextStyle( + fontSize: 8, fontWeight: FontWeight.w400), ), ], ), - Row( + const Row( children: [ BodyMedium( text: '1000.00 ', @@ -179,28 +178,29 @@ class PowerClampCard extends StatelessWidget { ), ], ), - SizedBox( - height: 290, - 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', + 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( 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 index 0e6a608..506cd2c 100644 --- a/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_page.dart @@ -24,11 +24,12 @@ 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() { @@ -54,16 +55,18 @@ class _PowerClampPageState extends State { 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(); - PowerClampModel model = _initialPowerClampModel(); 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) { @@ -83,11 +86,33 @@ class _PowerClampPageState extends State { onRefresh: () async { blocProvider.add(const PowerClampInitial()); }, - child: PageView( - controller: _pageController, - children: - _buildPowerClampCards(model, chartData, blocProvider), - ), + 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(), @@ -164,6 +189,7 @@ class _PowerClampPageState extends State { required PowerClampBloc blocProvider, }) { return PowerClampCard( + dateTimeSelected:blocProvider.formattedDate, energyConsumption: _getValueOrNA(phase.dataPoints, isGeneral ? 0 : 5), title: title, isGeneral: isGeneral, 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 index 79f1e73..4c0cab1 100644 --- a/lib/features/devices/view/widgets/power_clamp/power_clamp_test.dart +++ b/lib/features/devices/view/widgets/power_clamp/power_clamp_test.dart @@ -1,312 +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'; +// 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; +// class PowerClampTestPage extends StatefulWidget { +// late final DeviceModel? device; - PowerClampTestPage({super.key, this.device}); +// PowerClampTestPage({super.key, this.device}); - @override - _PowerClampTestPageState createState() => _PowerClampTestPageState(); -} +// @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; - }); - } - }); - } +// 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 +// 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 = []; +// @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: _blocProvider.dateSwitcher(), - 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), +// 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.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.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), +// 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), - ), - ); - }), - ), - ), - ], - ); - }, - ), - ), - ); - } -} +// ]), +// ), +// ), +// 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/services/api/devices_api.dart b/lib/services/api/devices_api.dart index bb58b42..87fcdb6 100644 --- a/lib/services/api/devices_api.dart +++ b/lib/services/api/devices_api.dart @@ -383,6 +383,7 @@ class DevicesAPI { .replaceAll('{startTime}', startTime) .replaceAll('{endTime}', endTime), expectedResponseModel: (json) { + log('json=====$json'); return DeviceReport.fromJson(json); }, ); From 34762dee6ee2b3fd393d0f3c2efe0874f7fd5045 Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 23 Oct 2024 16:40:12 +0300 Subject: [PATCH 10/12] power_clamp_chart_functionality --- .../devices/bloc/power_clamp_bloc/power_clamp_event.dart | 2 +- .../devices/bloc/power_clamp_bloc/power_clamp_state.dart | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) 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 index fa038ce..ea1fa76 100644 --- a/lib/features/devices/bloc/power_clamp_bloc/power_clamp_event.dart +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_event.dart @@ -113,7 +113,7 @@ class FilterRecordsByDateEvent extends PowerClampEvent { final DateTime selectedDate; final String viewType; // 'Day', 'Month', 'Year' - FilterRecordsByDateEvent( + 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 index 1fd6d51..4e08065 100644 --- a/lib/features/devices/bloc/power_clamp_bloc/power_clamp_state.dart +++ b/lib/features/devices/bloc/power_clamp_bloc/power_clamp_state.dart @@ -1,5 +1,4 @@ import 'package:equatable/equatable.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'; @@ -54,5 +53,5 @@ class EnergyDataState extends PowerClampState { class FilterRecordsState extends PowerClampState { final List filteredRecords; - FilterRecordsState({required this.filteredRecords}); + const FilterRecordsState({required this.filteredRecords}); } From 5844ea429b478198870f0a48c057928695c234bd Mon Sep 17 00:00:00 2001 From: mohammad Date: Wed, 23 Oct 2024 16:58:43 +0300 Subject: [PATCH 11/12] power_clamp_icon --- assets/icons/power_clamp.svg | 43 +++++++++++++++++++ .../door_sensor_bloc/door_sensor_bloc.dart | 1 - lib/features/devices/model/device_model.dart | 2 + lib/generated/assets.dart | 3 +- 4 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 assets/icons/power_clamp.svg 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/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/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/generated/assets.dart b/lib/generated/assets.dart index 1b0c77e..2d7e20a 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -1088,6 +1088,7 @@ class Assets { 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"; - //speedo_meter + //powerClampIcon } From 2be5d0e164f2d2ef353db38ec07aba2fcdf0bfe4 Mon Sep 17 00:00:00 2001 From: mohammad Date: Sun, 27 Oct 2024 10:56:21 +0300 Subject: [PATCH 12/12] search_icon --- assets/icons/search_icon.svg | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 assets/icons/search_icon.svg 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 @@ + + + +