Compare commits

..

17 Commits

Author SHA1 Message Date
3c9494963d Add generated configuration files for Flutter integration across platforms 2025-06-25 15:58:58 +03:00
f38ac58442 Add bloc closure handling and improve device status updates in AcBloc 2025-06-25 14:45:10 +03:00
487c5a894b Sp 1796 fe set the max on range of aqi chart based on selected pollutant s current highest value (#290)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1796](https://syncrow.atlassian.net/browse/SP-1796)

## Description

Added day of month labels to all analytics charts
Implemented ranges for each pollutant based on the highest value of each
pollutant in the range of aqi chart.

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [x]  New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [x]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1796]:
https://syncrow.atlassian.net/browse/SP-1796?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-25 12:42:55 +03:00
7e0200aad8 SP-1770-FE-Parent-nodes-in-community-tree-not-partially-selected-when… (#294)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1770](https://syncrow.atlassian.net/browse/SP-1770)

## Description

Space Tree Selection state reads from the correct list, based on if the
user was filtering or not.

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1770]:
https://syncrow.atlassian.net/browse/SP-1770?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-25 12:42:34 +03:00
52b843d514 SP-1770-FE-Parent-nodes-in-community-tree-not-partially-selected-when-selecting-space-from-sidebar. 2025-06-25 09:53:09 +03:00
423ad6e687 Enhance navigation buttons in SmartPowerDeviceControl for better user… (#293)
… experience

<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->



## Description

<!--- Describe your changes in detail -->
Enhance navigation buttons in SmartPowerDeviceControl 

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-06-25 09:13:33 +03:00
932e50f518 sp:1677 [FE] Device status in Control modal always shows "Online" regardless of actual status (#287)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->

## Jira Ticket
[SP-1677](https://syncrow.atlassian.net/browse/SP-1677)

## Description

status depend on the real status of the device afterit was static 

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore 


[SP-1677]:
https://syncrow.atlassian.net/browse/SP-1677?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2025-06-25 08:18:10 +03:00
c649044a1f Enhance navigation buttons in SmartPowerDeviceControl for better user experience 2025-06-24 16:40:42 +03:00
c46cfb04a7 Add countdown seconds to schedule management (#291)
<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->


## Description

<!--- Describe your changes in detail -->
Add countdown seconds to schedule management
## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-06-24 16:21:43 +03:00
8754960713 Cancel-button-on-"Create-Visitor-Password"-modal-unnecessarily-triggers-visitor-passwords-API (#292)
… on pop

<!--
  Thanks for contributing!

Provide a description of your changes below and a general summary in the
title

Please look at the following checklist to ensure that your PR can be
accepted quickly:
-->



## Description

<!--- Describe your changes in detail -->

## Type of Change

<!--- Put an `x` in all the boxes that apply: -->

- [ ]  New feature (non-breaking change which adds functionality)
- [x] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ]  Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 🧹 Code refactor
- [ ]  Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore
2025-06-24 16:21:03 +03:00
c6e98fa245 Refactor visitor password dialog navigation to return specific values on pop 2025-06-24 16:06:53 +03:00
f901983aa5 Implemented ranges for the values in the AQI chart based on the selected pollutant. 2025-06-24 15:01:25 +03:00
010403f1fa Added day of month axis name to all charts. 2025-06-24 14:50:22 +03:00
ee1ebeae2e Changed energy management charts titles for a more clear name. 2025-06-24 14:45:15 +03:00
6e6ef79ed0 enhanced heat map tooltip's message. 2025-06-24 14:44:56 +03:00
7e5825de45 Fixed typo in occupancy sidebar. 2025-06-24 14:44:45 +03:00
95d6e1ecda if online go green with online status else red with offline status 2025-06-23 16:33:45 +03:00
16 changed files with 146 additions and 63 deletions

View File

@ -18,7 +18,11 @@ abstract final class RangeOfAqiChartsHelper {
(ColorsManager.hazardousPurple, 'Hazardous'), (ColorsManager.hazardousPurple, 'Hazardous'),
]; ];
static FlTitlesData titlesData(BuildContext context, List<RangeOfAqi> data) { static FlTitlesData titlesData(
BuildContext context,
List<RangeOfAqi> data, {
double leftSideInterval = 50,
}) {
final titlesData = EnergyManagementChartsHelper.titlesData(context); final titlesData = EnergyManagementChartsHelper.titlesData(context);
return titlesData.copyWith( return titlesData.copyWith(
bottomTitles: titlesData.bottomTitles.copyWith( bottomTitles: titlesData.bottomTitles.copyWith(
@ -39,11 +43,11 @@ abstract final class RangeOfAqiChartsHelper {
leftTitles: titlesData.leftTitles.copyWith( leftTitles: titlesData.leftTitles.copyWith(
sideTitles: titlesData.leftTitles.sideTitles.copyWith( sideTitles: titlesData.leftTitles.sideTitles.copyWith(
reservedSize: 70, reservedSize: 70,
interval: 50, interval: leftSideInterval,
maxIncluded: false, maxIncluded: false,
minIncluded: true, minIncluded: true,
getTitlesWidget: (value, meta) { getTitlesWidget: (value, meta) {
final text = value >= 300 ? '301+' : value.toInt().toString(); final text = value.toInt().toString();
return Padding( return Padding(
padding: const EdgeInsetsDirectional.only(end: 12), padding: const EdgeInsetsDirectional.only(end: 12),
child: FittedBox( child: FittedBox(

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart'; import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
import 'package:syncrow_web/pages/analytics/widgets/charts_x_axis_title.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -149,6 +150,7 @@ class AqiDistributionChart extends StatelessWidget {
); );
final bottomTitles = AxisTitles( final bottomTitles = AxisTitles(
axisNameWidget: const ChartsXAxisTitle(),
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: chartData.isNotEmpty, showTitles: chartData.isNotEmpty,
getTitlesWidget: (value, _) => FittedBox( getTitlesWidget: (value, _) => FittedBox(

View File

@ -2,15 +2,18 @@ import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart'; import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/range_of_aqi_charts_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/range_of_aqi_charts_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class RangeOfAqiChart extends StatelessWidget { class RangeOfAqiChart extends StatelessWidget {
final List<RangeOfAqi> chartData; final List<RangeOfAqi> chartData;
final AqiType selectedAqiType;
const RangeOfAqiChart({ const RangeOfAqiChart({
super.key, super.key,
required this.chartData, required this.chartData,
required this.selectedAqiType,
}); });
List<(List<double> values, Color color, Color? dotColor)> get _lines { List<(List<double> values, Color color, Color? dotColor)> get _lines {
@ -45,15 +48,34 @@ class RangeOfAqiChart extends StatelessWidget {
]; ];
} }
(double maxY, double interval) get _maxYForAqiType {
const aqiMaxValues = <AqiType, (double maxY, double interval)>{
AqiType.aqi: (401, 100),
AqiType.pm25: (351, 50),
AqiType.pm10: (501, 100),
AqiType.hcho: (301, 50),
AqiType.tvoc: (501, 50),
AqiType.co2: (1251, 250),
};
return aqiMaxValues[selectedAqiType]!;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return LineChart( return LineChart(
LineChartData( LineChartData(
minY: 0, minY: 0,
maxY: 301, maxY: _maxYForAqiType.$1,
clipData: const FlClipData.vertical(), clipData: const FlClipData.vertical(),
gridData: EnergyManagementChartsHelper.gridData(horizontalInterval: 50), gridData: EnergyManagementChartsHelper.gridData(
titlesData: RangeOfAqiChartsHelper.titlesData(context, chartData), horizontalInterval: _maxYForAqiType.$2,
),
titlesData: RangeOfAqiChartsHelper.titlesData(
context,
chartData,
leftSideInterval: _maxYForAqiType.$2,
),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: RangeOfAqiChartsHelper.lineTouchData(chartData), lineTouchData: RangeOfAqiChartsHelper.lineTouchData(chartData),
betweenBarsData: [ betweenBarsData: [

View File

@ -32,7 +32,12 @@ class RangeOfAqiChartBox extends StatelessWidget {
const SizedBox(height: 10), const SizedBox(height: 10),
const Divider(), const Divider(),
const SizedBox(height: 20), const SizedBox(height: 20),
Expanded(child: RangeOfAqiChart(chartData: state.filteredRangeOfAqi)), Expanded(
child: RangeOfAqiChart(
chartData: state.filteredRangeOfAqi,
selectedAqiType: state.selectedAqiType,
),
),
], ],
), ),
); );

View File

@ -1,6 +1,7 @@
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/helpers/format_number_to_kwh.dart'; import 'package:syncrow_web/pages/analytics/helpers/format_number_to_kwh.dart';
import 'package:syncrow_web/pages/analytics/widgets/charts_x_axis_title.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -15,6 +16,7 @@ abstract final class EnergyManagementChartsHelper {
return FlTitlesData( return FlTitlesData(
show: true, show: true,
bottomTitles: AxisTitles( bottomTitles: AxisTitles(
axisNameWidget: const ChartsXAxisTitle(),
drawBelowEverything: true, drawBelowEverything: true,
sideTitles: SideTitles( sideTitles: SideTitles(
interval: 1, interval: 1,
@ -62,17 +64,12 @@ abstract final class EnergyManagementChartsHelper {
); );
} }
static String getToolTipLabel(num month, double value) { static String getToolTipLabel(double value) => value.formatNumberToKwh;
final monthLabel = month.toString();
final valueLabel = value.formatNumberToKwh;
final labels = [monthLabel, valueLabel];
return labels.where((element) => element.isNotEmpty).join(', ');
}
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) { static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
return touchedSpots.map((spot) { return touchedSpots.map((spot) {
return LineTooltipItem( return LineTooltipItem(
getToolTipLabel(spot.x, spot.y), getToolTipLabel(spot.y),
const TextStyle( const TextStyle(
color: ColorsManager.textPrimaryColor, color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,

View File

@ -37,7 +37,7 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
child: ChartTitle( child: ChartTitle(
title: Text('Energy Consumption per Device'), title: Text('Device energy consumed'),
), ),
), ),
), ),

View File

@ -32,7 +32,7 @@ class TotalEnergyConsumptionChartBox extends StatelessWidget {
child: FittedBox( child: FittedBox(
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: ChartTitle(title: Text('Total Energy Consumption')), child: ChartTitle(title: Text('Space energy consumed')),
), ),
), ),
const Spacer(flex: 4), const Spacer(flex: 4),

View File

@ -39,7 +39,7 @@ class HeatMapTooltip extends StatelessWidget {
), ),
const Divider(height: 2, thickness: 1), const Divider(height: 2, thickness: 1),
Text( Text(
'$value Occupants', 'Occupancy detected: $value',
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,

View File

@ -2,6 +2,7 @@ import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/models/occupacy.dart'; import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
import 'package:syncrow_web/pages/analytics/widgets/charts_x_axis_title.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -88,8 +89,8 @@ class OccupancyChart extends StatelessWidget {
}) { }) {
final data = chartData; final data = chartData;
final occupancyValue = double.parse(data[group.x.toInt()].occupancy); final occupancyValue = double.parse(data[group.x].occupancy);
final percentage = '${(occupancyValue).toStringAsFixed(0)}%'; final percentage = '${occupancyValue.toStringAsFixed(0)}%';
return BarTooltipItem( return BarTooltipItem(
percentage, percentage,
@ -116,7 +117,7 @@ class OccupancyChart extends StatelessWidget {
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: Text( child: Text(
'${(value).toStringAsFixed(0)}%', '${value.toStringAsFixed(0)}%',
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
fontSize: 12, fontSize: 12,
color: ColorsManager.greyColor, color: ColorsManager.greyColor,
@ -128,6 +129,7 @@ class OccupancyChart extends StatelessWidget {
); );
final bottomTitles = AxisTitles( final bottomTitles = AxisTitles(
axisNameWidget: const ChartsXAxisTitle(),
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: true, showTitles: true,
getTitlesWidget: (value, _) => FittedBox( getTitlesWidget: (value, _) => FittedBox(

View File

@ -23,7 +23,7 @@ class OccupancyEndSideBar extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const AnalyticsSidebarHeader(title: 'Presnce Sensor'), const AnalyticsSidebarHeader(title: 'Presence Sensor'),
Expanded( Expanded(
child: SizedBox( child: SizedBox(
// height: MediaQuery.sizeOf(context).height * 0.2, // height: MediaQuery.sizeOf(context).height * 0.2,

View File

@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ChartsXAxisTitle extends StatelessWidget {
const ChartsXAxisTitle({
this.label = 'Day of month',
super.key,
});
final String label;
@override
Widget build(BuildContext context) {
return Text(
label,
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.lightGreyColor,
fontSize: 8,
),
);
}
}

View File

@ -45,7 +45,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
) async { ) async {
emit(AcsLoadingState()); emit(AcsLoadingState());
try { try {
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId); final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
if (deviceStatus.countdown1 != 0) { if (deviceStatus.countdown1 != 0) {
final totalMinutes = deviceStatus.countdown1 * 6; final totalMinutes = deviceStatus.countdown1 * 6;
@ -68,12 +69,13 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} }
} }
void _listenToChanges(deviceId) { StreamSubscription<DatabaseEvent>? _deviceStatusSubscription;
void _listenToChanges(String deviceId) {
try { try {
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
final stream = ref.onValue; _deviceStatusSubscription =
ref.onValue.listen((DatabaseEvent event) async {
stream.listen((DatabaseEvent event) async {
if (event.snapshot.value == null) return; if (event.snapshot.value == null) return;
Map<dynamic, dynamic> usersMap = Map<dynamic, dynamic> usersMap =
@ -82,10 +84,14 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
List<Status> statusList = []; List<Status> statusList = [];
usersMap['status'].forEach((element) { usersMap['status'].forEach((element) {
statusList.add(Status(code: element['code'], value: element['value'])); statusList
.add(Status(code: element['code'], value: element['value']));
}); });
deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList); deviceStatus =
AcStatusModel.fromJson(usersMap['productUuid'], statusList);
print('Device status updated: ${deviceStatus.acSwitch}');
if (!isClosed) { if (!isClosed) {
add(AcStatusUpdated(deviceStatus)); add(AcStatusUpdated(deviceStatus));
} }
@ -106,15 +112,14 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
Emitter<AcsState> emit, Emitter<AcsState> emit,
) async { ) async {
emit(AcsLoadingState()); emit(AcsLoadingState());
_updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus));
try { try {
final success = await controlDeviceService.controlDevice( final success = await controlDeviceService.controlDevice(
deviceUuid: event.deviceId, deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value), status: Status(code: event.code, value: event.value),
); );
_updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus));
if (!success) { if (!success) {
emit(const AcsFailedState(error: 'Failed to control device')); emit(const AcsFailedState(error: 'Failed to control device'));
} }
@ -129,8 +134,10 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
) async { ) async {
emit(AcsLoadingState()); emit(AcsLoadingState());
try { try {
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds); final status =
deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status); await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus =
AcStatusModel.fromJson(event.devicesIds.first, status.status);
emit(ACStatusLoaded(status: deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} catch (e) { } catch (e) {
emit(AcsFailedState(error: e.toString())); emit(AcsFailedState(error: e.toString()));
@ -293,14 +300,18 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
totalSeconds--; totalSeconds--;
scheduledHours = totalSeconds ~/ 3600; scheduledHours = totalSeconds ~/ 3600;
scheduledMinutes = (totalSeconds % 3600) ~/ 60; scheduledMinutes = (totalSeconds % 3600) ~/ 60;
if (!isClosed) {
add(UpdateTimerEvent()); add(UpdateTimerEvent());
}
} else { } else {
_countdownTimer?.cancel(); _countdownTimer?.cancel();
timerActive = false; timerActive = false;
scheduledHours = 0; scheduledHours = 0;
scheduledMinutes = 0; scheduledMinutes = 0;
if (!isClosed) {
add(TimerCompletedEvent()); add(TimerCompletedEvent());
} }
}
}); });
} }
@ -326,9 +337,11 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
_startCountdownTimer( _startCountdownTimer(
emit, emit,
); );
if (!isClosed) {
add(UpdateTimerEvent()); add(UpdateTimerEvent());
} }
} }
}
void _updateDeviceFunctionFromCode(String code, dynamic value) { void _updateDeviceFunctionFromCode(String code, dynamic value) {
switch (code) { switch (code) {
@ -370,6 +383,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
@override @override
Future<void> close() { Future<void> close() {
add(OnClose()); add(OnClose());
_countdownTimer?.cancel();
_deviceStatusSubscription?.cancel();
return super.close(); return super.close();
} }
} }

View File

@ -12,7 +12,8 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
//Smart Power Clamp //Smart Power Clamp
class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayout { class SmartPowerDeviceControl extends StatelessWidget
with HelperResponsiveLayout {
final String deviceId; final String deviceId;
const SmartPowerDeviceControl({super.key, required this.deviceId}); const SmartPowerDeviceControl({super.key, required this.deviceId});
@ -145,8 +146,11 @@ class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayou
children: [ children: [
IconButton( IconButton(
icon: const Icon(Icons.arrow_left), icon: const Icon(Icons.arrow_left),
onPressed: () { onPressed: blocProvider.currentPage <= 0
blocProvider.add(SmartPowerArrowPressedEvent(-1)); ? null
: () {
blocProvider
.add(SmartPowerArrowPressedEvent(-1));
pageController.previousPage( pageController.previousPage(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut, curve: Curves.easeInOut,
@ -165,8 +169,11 @@ class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayou
), ),
IconButton( IconButton(
icon: const Icon(Icons.arrow_right), icon: const Icon(Icons.arrow_right),
onPressed: () { onPressed: blocProvider.currentPage >= 3
blocProvider.add(SmartPowerArrowPressedEvent(1)); ? null
: () {
blocProvider
.add(SmartPowerArrowPressedEvent(1));
pageController.nextPage( pageController.nextPage(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut, curve: Curves.easeInOut,
@ -195,8 +202,8 @@ class SmartPowerDeviceControl extends StatelessWidget with HelperResponsiveLayou
blocProvider.add(SelectDateEvent(context: context)); blocProvider.add(SelectDateEvent(context: context));
blocProvider.add(FilterRecordsByDateEvent( blocProvider.add(FilterRecordsByDateEvent(
selectedDate: blocProvider.dateTime!, selectedDate: blocProvider.dateTime!,
viewType: viewType: blocProvider
blocProvider.views[blocProvider.currentIndex])); .views[blocProvider.currentIndex]));
}, },
widget: blocProvider.dateSwitcher(), widget: blocProvider.dateSwitcher(),
chartData: blocProvider.energyDataList.isNotEmpty chartData: blocProvider.energyDataList.isNotEmpty

View File

@ -79,6 +79,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
} }
Widget _buildDeviceInfoSection() { Widget _buildDeviceInfoSection() {
final isOnlineDevice = device.online != null && device.online!;
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 50), padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 50),
child: Table( child: Table(
@ -107,7 +108,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
'Installation Date and Time:', 'Installation Date and Time:',
formatDateTime( formatDateTime(
DateTime.fromMillisecondsSinceEpoch( DateTime.fromMillisecondsSinceEpoch(
((device.createTime ?? 0) * 1000), (device.createTime ?? 0) * 1000,
), ),
), ),
), ),
@ -126,12 +127,16 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
), ),
TableRow( TableRow(
children: [ children: [
_buildInfoRow('Status:', 'Online', statusColor: Colors.green), _buildInfoRow(
'Status:',
isOnlineDevice ? 'Online' : 'offline',
statusColor: isOnlineDevice ? Colors.green : Colors.red,
),
_buildInfoRow( _buildInfoRow(
'Last Offline Date and Time:', 'Last Offline Date and Time:',
formatDateTime( formatDateTime(
DateTime.fromMillisecondsSinceEpoch( DateTime.fromMillisecondsSinceEpoch(
((device.updateTime ?? 0) * 1000), (device.updateTime ?? 0) * 1000,
), ),
), ),
), ),

View File

@ -289,7 +289,6 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
selectedSpaces: updatedSelectedSpaces, selectedSpaces: updatedSelectedSpaces,
soldCheck: updatedSoldChecks, soldCheck: updatedSoldChecks,
selectedCommunityAndSpaces: communityAndSpaces)); selectedCommunityAndSpaces: communityAndSpaces));
emit(state.copyWith(selectedSpaces: updatedSelectedSpaces));
} catch (e) { } catch (e) {
emit(const SpaceTreeErrorState('Something went wrong')); emit(const SpaceTreeErrorState('Something went wrong'));
} }
@ -445,10 +444,12 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
List<String> _getThePathToChild(String communityId, String selectedSpaceId) { List<String> _getThePathToChild(String communityId, String selectedSpaceId) {
List<String> ids = []; List<String> ids = [];
for (var community in state.communityList) { final communityDataSource =
state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList;
for (final community in communityDataSource) {
if (community.uuid == communityId) { if (community.uuid == communityId) {
for (var space in community.spaces) { for (final space in community.spaces) {
List<String> list = []; final list = <String>[];
list.add(space.uuid!); list.add(space.uuid!);
ids = _getAllParentsIds(space, selectedSpaceId, List.from(list)); ids = _getAllParentsIds(space, selectedSpaceId, List.from(list));
if (ids.isNotEmpty) { if (ids.isNotEmpty) {

View File

@ -102,7 +102,7 @@ class VisitorPasswordDialog extends StatelessWidget {
], ],
)) ))
.then((v) { .then((v) {
Navigator.of(context).pop(true); Navigator.of(context).pop(v);
}); });
} else if (state is FailedState) { } else if (state is FailedState) {
visitorBloc.stateDialog( visitorBloc.stateDialog(
@ -476,7 +476,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: DefaultButton( child: DefaultButton(
borderRadius: 8, borderRadius: 8,
onPressed: () { onPressed: () {
Navigator.of(context).pop(true); Navigator.of(context).pop(null);
}, },
backgroundColor: Colors.white, backgroundColor: Colors.white,
child: Text( child: Text(
@ -651,7 +651,7 @@ class VisitorPasswordDialog extends StatelessWidget {
child: DefaultButton( child: DefaultButton(
borderRadius: 8, borderRadius: 8,
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop(null);
}, },
backgroundColor: Colors.white, backgroundColor: Colors.white,
child: Text( child: Text(