Compare commits

..

10 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
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
14 changed files with 271 additions and 247 deletions

View File

@ -18,7 +18,11 @@ abstract final class RangeOfAqiChartsHelper {
(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);
return titlesData.copyWith(
bottomTitles: titlesData.bottomTitles.copyWith(
@ -39,11 +43,11 @@ abstract final class RangeOfAqiChartsHelper {
leftTitles: titlesData.leftTitles.copyWith(
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
reservedSize: 70,
interval: 50,
interval: leftSideInterval,
maxIncluded: false,
minIncluded: true,
getTitlesWidget: (value, meta) {
final text = value >= 300 ? '301+' : value.toInt().toString();
final text = value.toInt().toString();
return Padding(
padding: const EdgeInsetsDirectional.only(end: 12),
child: FittedBox(

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.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/widgets/charts_x_axis_title.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -149,6 +150,7 @@ class AqiDistributionChart extends StatelessWidget {
);
final bottomTitles = AxisTitles(
axisNameWidget: const ChartsXAxisTitle(),
sideTitles: SideTitles(
showTitles: chartData.isNotEmpty,
getTitlesWidget: (value, _) => FittedBox(

View File

@ -2,15 +2,18 @@ import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.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/widgets/aqi_type_dropdown.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class RangeOfAqiChart extends StatelessWidget {
final List<RangeOfAqi> chartData;
final AqiType selectedAqiType;
const RangeOfAqiChart({
super.key,
required this.chartData,
required this.selectedAqiType,
});
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
Widget build(BuildContext context) {
return LineChart(
LineChartData(
minY: 0,
maxY: 301,
maxY: _maxYForAqiType.$1,
clipData: const FlClipData.vertical(),
gridData: EnergyManagementChartsHelper.gridData(horizontalInterval: 50),
titlesData: RangeOfAqiChartsHelper.titlesData(context, chartData),
gridData: EnergyManagementChartsHelper.gridData(
horizontalInterval: _maxYForAqiType.$2,
),
titlesData: RangeOfAqiChartsHelper.titlesData(
context,
chartData,
leftSideInterval: _maxYForAqiType.$2,
),
borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: RangeOfAqiChartsHelper.lineTouchData(chartData),
betweenBarsData: [

View File

@ -32,7 +32,12 @@ class RangeOfAqiChartBox extends StatelessWidget {
const SizedBox(height: 10),
const Divider(),
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:flutter/material.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/extension/build_context_x.dart';
@ -15,6 +16,7 @@ abstract final class EnergyManagementChartsHelper {
return FlTitlesData(
show: true,
bottomTitles: AxisTitles(
axisNameWidget: const ChartsXAxisTitle(),
drawBelowEverything: true,
sideTitles: SideTitles(
interval: 1,
@ -62,17 +64,12 @@ abstract final class EnergyManagementChartsHelper {
);
}
static String getToolTipLabel(num month, double value) {
final monthLabel = month.toString();
final valueLabel = value.formatNumberToKwh;
final labels = [monthLabel, valueLabel];
return labels.where((element) => element.isNotEmpty).join(', ');
}
static String getToolTipLabel(double value) => value.formatNumberToKwh;
static List<LineTooltipItem?> getTooltipItems(List<LineBarSpot> touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
getToolTipLabel(spot.x, spot.y),
getToolTipLabel(spot.y),
const TextStyle(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w600,

View File

@ -37,7 +37,7 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerStart,
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(
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown,
child: ChartTitle(title: Text('Total Energy Consumption')),
child: ChartTitle(title: Text('Space energy consumed')),
),
),
const Spacer(flex: 4),

View File

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

View File

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

View File

@ -23,7 +23,7 @@ class OccupancyEndSideBar extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const AnalyticsSidebarHeader(title: 'Presnce Sensor'),
const AnalyticsSidebarHeader(title: 'Presence Sensor'),
Expanded(
child: SizedBox(
// 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

@ -50,9 +50,6 @@ class _DynamicTableState extends State<DynamicTable> {
bool _selectAll = false;
final ScrollController _verticalScrollController = ScrollController();
final ScrollController _horizontalScrollController = ScrollController();
static const double _fixedRowHeight = 60;
static const double _checkboxColumnWidth = 50;
static const double _settingsColumnWidth = 100;
@override
void initState() {
@ -70,6 +67,7 @@ class _DynamicTableState extends State<DynamicTable> {
bool _compareListOfLists(
List<List<dynamic>> oldList, List<List<dynamic>> newList) {
// Check if the old and new lists are the same
if (oldList.length != newList.length) return false;
for (int i = 0; i < oldList.length; i++) {
@ -106,127 +104,69 @@ class _DynamicTableState extends State<DynamicTable> {
context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows));
}
double get _totalTableWidth {
final hasSettings = widget.headers.contains('Settings');
final base = (widget.withCheckBox ? _checkboxColumnWidth : 0) +
(hasSettings ? _settingsColumnWidth : 0);
final regularCount = widget.headers.length - (hasSettings ? 1 : 0);
final regularWidth = (widget.size.width - base) / regularCount;
return base + regularCount * regularWidth;
}
@override
Widget build(BuildContext context) {
return Container(
width: widget.size.width,
height: widget.size.height,
decoration: widget.cellDecoration,
child: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(scrollbars: false),
child: Scrollbar(
controller: _verticalScrollController,
thumbVisibility: true,
trackVisibility: true,
child: Scrollbar(
//fixed the horizontal scrollbar issue
controller: _horizontalScrollController,
thumbVisibility: true,
trackVisibility: true,
notificationPredicate: (notif) =>
notif.metrics.axis == Axis.horizontal,
notificationPredicate: (notif) => notif.depth == 1,
child: SingleChildScrollView(
controller: _verticalScrollController,
child: SingleChildScrollView(
controller: _horizontalScrollController,
scrollDirection: Axis.horizontal,
child: SizedBox(
width: _totalTableWidth,
width: widget.size.width,
child: Column(
children: [
Container(
height: _fixedRowHeight,
decoration: widget.headerDecoration ??
const BoxDecoration(color: ColorsManager.boxColor),
const BoxDecoration(
color: ColorsManager.boxColor,
),
child: Row(
children: [
if (widget.withCheckBox)
_buildSelectAllCheckbox(_checkboxColumnWidth),
for (var i = 0; i < widget.headers.length; i++)
_buildTableHeaderCell(
widget.headers[i],
widget.headers[i] == 'Settings'
? _settingsColumnWidth
: (_totalTableWidth -
(widget.withCheckBox
? _checkboxColumnWidth
: 0) -
(widget.headers.contains('Settings')
? _settingsColumnWidth
: 0)) /
(widget.headers.length -
(widget.headers.contains('Settings')
? 1
: 0)),
),
if (widget.withCheckBox) _buildSelectAllCheckbox(),
...List.generate(widget.headers.length, (index) {
return _buildTableHeaderCell(
widget.headers[index], index);
})
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
],
),
),
Expanded(
SizedBox(
width: widget.size.width,
child: widget.isEmpty
? _buildEmptyState()
: Scrollbar(
controller: _verticalScrollController,
thumbVisibility: true,
trackVisibility: true,
notificationPredicate: (notif) =>
notif.metrics.axis == Axis.vertical,
child: ListView.builder(
controller: _verticalScrollController,
itemCount: widget.data.length,
itemBuilder: (_, rowIndex) {
: Column(
children:
List.generate(widget.data.length, (rowIndex) {
final row = widget.data[rowIndex];
return SizedBox(
height: _fixedRowHeight,
child: Row(
return Row(
children: [
if (widget.withCheckBox)
_buildRowCheckbox(
rowIndex,
_checkboxColumnWidth,
),
for (var colIndex = 0;
colIndex < row.length;
colIndex++)
widget.headers[colIndex] == 'Settings'
? buildSettingsIcon(
width: _settingsColumnWidth,
onTap: () => widget
.onSettingsPressed
?.call(rowIndex),
)
: _buildTableCell(
row[colIndex].toString(),
width: widget.headers[
colIndex] ==
'Settings'
? _settingsColumnWidth
: (_totalTableWidth -
(widget.withCheckBox
? _checkboxColumnWidth
: 0) -
(widget.headers
.contains(
'Settings')
? _settingsColumnWidth
: 0)) /
(widget.headers.length -
(widget.headers
.contains(
'Settings')
? 1
: 0)),
rowIndex, widget.size.height * 0.08),
...row.asMap().entries.map((entry) {
return _buildTableCell(
entry.value.toString(),
widget.size.height * 0.08,
rowIndex: rowIndex,
columnIndex: colIndex,
),
],
),
columnIndex: entry.key,
);
},
),
}).toList(),
],
);
}),
),
),
],
@ -235,6 +175,7 @@ class _DynamicTableState extends State<DynamicTable> {
),
),
),
),
);
}
@ -269,10 +210,9 @@ class _DynamicTableState extends State<DynamicTable> {
],
),
);
Widget _buildSelectAllCheckbox(double width) {
Widget _buildSelectAllCheckbox() {
return Container(
width: width,
width: 50,
decoration: const BoxDecoration(
border: Border.symmetric(
vertical: BorderSide(color: ColorsManager.boxDivider),
@ -287,11 +227,11 @@ class _DynamicTableState extends State<DynamicTable> {
);
}
Widget _buildRowCheckbox(int index, double width) {
Widget _buildRowCheckbox(int index, double size) {
return Container(
width: width,
width: 50,
padding: const EdgeInsets.all(8.0),
height: _fixedRowHeight,
height: size,
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
@ -313,18 +253,20 @@ class _DynamicTableState extends State<DynamicTable> {
);
}
Widget _buildTableHeaderCell(String title, double width) {
return Container(
width: width,
Widget _buildTableHeaderCell(String title, int index) {
return Expanded(
child: Container(
decoration: const BoxDecoration(
border: Border.symmetric(
vertical: BorderSide(color: ColorsManager.boxDivider),
),
),
constraints: BoxConstraints(minHeight: 40, maxHeight: _fixedRowHeight),
constraints: const BoxConstraints.expand(height: 40),
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
padding: EdgeInsets.symmetric(
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
vertical: 4),
child: Text(
title,
style: context.textTheme.titleSmall!.copyWith(
@ -333,27 +275,28 @@ class _DynamicTableState extends State<DynamicTable> {
fontWeight: FontWeight.w400,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
),
);
}
Widget _buildTableCell(String content,
{required double width,
required int rowIndex,
required int columnIndex}) {
Widget _buildTableCell(String content, double size,
{required int rowIndex, required int columnIndex}) {
bool isBatteryLevel = content.endsWith('%');
double? batteryLevel;
if (isBatteryLevel) {
batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
}
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
if (isSettingsColumn) {
return buildSettingsIcon(
width: width, onTap: () => widget.onSettingsPressed?.call(rowIndex));
width: 120,
height: 60,
iconSize: 40,
onTap: () => widget.onSettingsPressed?.call(rowIndex),
);
}
Color? statusColor;
@ -377,10 +320,10 @@ class _DynamicTableState extends State<DynamicTable> {
statusColor = Colors.black;
}
return Container(
width: width,
height: _fixedRowHeight,
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4),
return Expanded(
child: Container(
height: size,
padding: const EdgeInsets.all(5.0),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
@ -400,19 +343,23 @@ class _DynamicTableState extends State<DynamicTable> {
? ColorsManager.green
: statusColor,
fontSize: 13,
fontWeight: FontWeight.w400,
),
fontWeight: FontWeight.w400),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
);
}
Widget buildSettingsIcon({required double width, VoidCallback? onTap}) {
return Container(
width: width,
height: _fixedRowHeight,
padding: const EdgeInsets.only(left: 15, top: 10, bottom: 10),
Widget buildSettingsIcon(
{double width = 120,
double height = 60,
double iconSize = 40,
VoidCallback? onTap}) {
return Column(
children: [
Container(
padding: const EdgeInsets.only(top: 10, bottom: 15, left: 10),
margin: const EdgeInsets.only(right: 15),
decoration: const BoxDecoration(
color: ColorsManager.whiteColors,
border: Border(
@ -422,13 +369,17 @@ class _DynamicTableState extends State<DynamicTable> {
),
),
),
child: Align(
alignment: Alignment.centerLeft,
width: width,
child: Padding(
padding: const EdgeInsets.only(
right: 16.0,
left: 17.0,
),
child: Container(
width: 50,
decoration: BoxDecoration(
color: const Color(0xFFF7F8FA),
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(height / 2),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.17),
@ -440,12 +391,12 @@ class _DynamicTableState extends State<DynamicTable> {
child: InkWell(
onTap: onTap,
child: Padding(
padding: EdgeInsets.all(8.0),
padding: const EdgeInsets.all(8.0),
child: Center(
child: SvgPicture.asset(
Assets.settings,
width: 40,
height: 20,
height: 22,
color: ColorsManager.primaryColor,
),
),
@ -453,6 +404,8 @@ class _DynamicTableState extends State<DynamicTable> {
),
),
),
),
],
);
}
}

View File

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

View File

@ -289,7 +289,6 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
selectedSpaces: updatedSelectedSpaces,
soldCheck: updatedSoldChecks,
selectedCommunityAndSpaces: communityAndSpaces));
emit(state.copyWith(selectedSpaces: updatedSelectedSpaces));
} catch (e) {
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> 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) {
for (var space in community.spaces) {
List<String> list = [];
for (final space in community.spaces) {
final list = <String>[];
list.add(space.uuid!);
ids = _getAllParentsIds(space, selectedSpaceId, List.from(list));
if (ids.isNotEmpty) {