Compare commits

..

1 Commits

14 changed files with 246 additions and 270 deletions

View File

@ -18,11 +18,7 @@ abstract final class RangeOfAqiChartsHelper {
(ColorsManager.hazardousPurple, 'Hazardous'), (ColorsManager.hazardousPurple, 'Hazardous'),
]; ];
static FlTitlesData titlesData( static FlTitlesData titlesData(BuildContext context, List<RangeOfAqi> data) {
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(
@ -43,11 +39,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: leftSideInterval, interval: 50,
maxIncluded: false, maxIncluded: false,
minIncluded: true, minIncluded: true,
getTitlesWidget: (value, meta) { getTitlesWidget: (value, meta) {
final text = value.toInt().toString(); final text = value >= 300 ? '301+' : 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,7 +3,6 @@ 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';
@ -150,7 +149,6 @@ 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,18 +2,15 @@ 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 {
@ -48,34 +45,15 @@ 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: _maxYForAqiType.$1, maxY: 301,
clipData: const FlClipData.vertical(), clipData: const FlClipData.vertical(),
gridData: EnergyManagementChartsHelper.gridData( gridData: EnergyManagementChartsHelper.gridData(horizontalInterval: 50),
horizontalInterval: _maxYForAqiType.$2, titlesData: RangeOfAqiChartsHelper.titlesData(context, chartData),
),
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,12 +32,7 @@ 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( Expanded(child: RangeOfAqiChart(chartData: state.filteredRangeOfAqi)),
child: RangeOfAqiChart(
chartData: state.filteredRangeOfAqi,
selectedAqiType: state.selectedAqiType,
),
),
], ],
), ),
); );

View File

@ -1,7 +1,6 @@
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';
@ -16,7 +15,6 @@ 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,
@ -64,12 +62,17 @@ abstract final class EnergyManagementChartsHelper {
); );
} }
static String getToolTipLabel(double value) => value.formatNumberToKwh; 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 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.y), getToolTipLabel(spot.x, 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('Device energy consumed'), title: Text('Energy Consumption per Device'),
), ),
), ),
), ),

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('Space energy consumed')), child: ChartTitle(title: Text('Total Energy Consumption')),
), ),
), ),
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(
'Occupancy detected: $value', '$value Occupants',
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,

View File

@ -2,7 +2,6 @@ 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';
@ -89,8 +88,8 @@ class OccupancyChart extends StatelessWidget {
}) { }) {
final data = chartData; final data = chartData;
final occupancyValue = double.parse(data[group.x].occupancy); final occupancyValue = double.parse(data[group.x.toInt()].occupancy);
final percentage = '${occupancyValue.toStringAsFixed(0)}%'; final percentage = '${(occupancyValue).toStringAsFixed(0)}%';
return BarTooltipItem( return BarTooltipItem(
percentage, percentage,
@ -117,7 +116,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,
@ -129,7 +128,6 @@ 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: 'Presence Sensor'), const AnalyticsSidebarHeader(title: 'Presnce Sensor'),
Expanded( Expanded(
child: SizedBox( child: SizedBox(
// height: MediaQuery.sizeOf(context).height * 0.2, // height: MediaQuery.sizeOf(context).height * 0.2,

View File

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

View File

@ -45,8 +45,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
) async { ) async {
emit(AcsLoadingState()); emit(AcsLoadingState());
try { try {
final status = final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
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;
@ -69,13 +68,12 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} }
} }
StreamSubscription<DatabaseEvent>? _deviceStatusSubscription; void _listenToChanges(deviceId) {
void _listenToChanges(String deviceId) {
try { try {
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
_deviceStatusSubscription = final stream = ref.onValue;
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 =
@ -84,14 +82,10 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
List<Status> statusList = []; List<Status> statusList = [];
usersMap['status'].forEach((element) { usersMap['status'].forEach((element) {
statusList statusList.add(Status(code: element['code'], value: element['value']));
.add(Status(code: element['code'], value: element['value']));
}); });
deviceStatus = deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
AcStatusModel.fromJson(usersMap['productUuid'], statusList);
print('Device status updated: ${deviceStatus.acSwitch}');
if (!isClosed) { if (!isClosed) {
add(AcStatusUpdated(deviceStatus)); add(AcStatusUpdated(deviceStatus));
} }
@ -112,14 +106,15 @@ 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'));
} }
@ -134,10 +129,8 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
) async { ) async {
emit(AcsLoadingState()); emit(AcsLoadingState());
try { try {
final status = final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status);
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()));
@ -300,17 +293,13 @@ 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());
}
} }
}); });
} }
@ -337,9 +326,7 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
_startCountdownTimer( _startCountdownTimer(
emit, emit,
); );
if (!isClosed) { add(UpdateTimerEvent());
add(UpdateTimerEvent());
}
} }
} }
@ -383,8 +370,6 @@ 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

@ -289,6 +289,7 @@ 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'));
} }
@ -444,12 +445,10 @@ 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 = [];
final communityDataSource = for (var community in state.communityList) {
state.searchQuery.isNotEmpty ? state.filteredCommunity : state.communityList;
for (final community in communityDataSource) {
if (community.uuid == communityId) { if (community.uuid == communityId) {
for (final space in community.spaces) { for (var space in community.spaces) {
final list = <String>[]; List<String> list = [];
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) {