Compare commits

..

25 Commits

Author SHA1 Message Date
06b320a75d move icon to the center and change subspace title name 2025-05-21 10:16:12 +03:00
a15b5439f0 Refactor user dropdown menu to display user's full name and arrow icon in a row for better layout consistency 2025-05-20 16:39:10 +03:00
4ded7d5202 Merge pull request #194 from SyncrowIOT/SP-1448-FE-Use-SliderValueSelector-widget-for-all-slider-widgets-in-Web-Routine
add step parameter in onTapFunction.
2025-05-19 11:37:56 +03:00
0d45a155e3 add step parameter in onTapFunction.
Add dialogType parameter in WaterHeaterPresenceSensor and CeilingSensorDialog.
Update step parameter in FlushValueSelectorWidget.
Update step parameter in FunctionBloc and WaterHeaterFunctions.
Update step, unit, min, and max parameters in ACFunction subclasses.
2025-05-19 11:22:15 +03:00
17aad13b2a Merge pull request #193 from SyncrowIOT/feature/make_analytics_date_picker_not_show_future_dates
Feature/make_analytics_date_picker_not_show_future_dates
2025-05-15 16:58:25 +03:00
a849c1dafb removed unused import. 2025-05-15 16:31:11 +03:00
3e3e17019a format. 2025-05-15 16:22:54 +03:00
b1bae3cb15 fixed overflow bug on charts. 2025-05-15 15:59:02 +03:00
051bf657ed Changed background color of analytics date pickers to match the design language of the platform. 2025-05-15 15:29:09 +03:00
5191c1e456 Performed selection validation, and made future dates disabled. 2025-05-15 15:28:36 +03:00
7a073f10aa Merge pull request #189 from SyncrowIOT/1495-calendar-bugfixes
1495 calendar bugfixes
2025-05-15 14:31:11 +03:00
900d47faae Merge pull request #190 from SyncrowIOT/SP-1506-FE-implement-chart-per-phase
SP-1506-FE-chart per phase api integration.
2025-05-15 14:30:58 +03:00
e35a7fdc70 Merge pull request #192 from SyncrowIOT/bugfix/charts-horizontal-lines
bugfix/charts-horizontal-lines
2025-05-15 14:30:37 +03:00
d80f5e1f3a Refactor energy consumption charts to enhance grid data configuration
Updated the grid data for EnergyConsumptionByPhasesChart, EnergyConsumptionPerDeviceChart, and TotalEnergyConsumptionChart to include horizontal line visibility and set a horizontal interval of 250. Removed unused phasesJson constant from TotalEnergyConsumptionChart for cleaner code.
2025-05-15 14:25:13 +03:00
c07b53107e SP-1506-FE-chart per phase api integration. 2025-05-15 10:51:09 +03:00
39d125ac7e loads energy management data on date changed. 2025-05-15 10:11:55 +03:00
ad15d0e138 loads occupancy chart on date changed. 2025-05-15 10:08:41 +03:00
e6d272a60d loads heatmap data on calendar change. 2025-05-15 10:06:13 +03:00
8dfe8d10d4 removed requestType from query parameters of RemoteOccupancyAnalyticsDevicesService._makeRequest. 2025-05-15 10:01:43 +03:00
5279020d08 Merge pull request #188 from SyncrowIOT/1495-energy-consumption-per-device-api-integration
1495-energy-consumption-per-device-api-integration.
2025-05-15 09:32:15 +03:00
da481536c4 1495-energy-consumption-per-device-api-integration. 2025-05-14 16:55:28 +03:00
f21366268a Merge pull request #187 from SyncrowIOT/SP-1509-FE-Implement-devices-status-based-on-the-selected-device-from-the-dropdown-list
Sp 1509 fe implement devices status based on the selected device from the dropdown list
2025-05-14 16:18:51 +03:00
c3aef736fd Merge pull request #186 from SyncrowIOT/1511-occupancy-heat-map-tooltip
1511-occupancy-heat-map-tooltip.
2025-05-14 16:18:08 +03:00
d45ff262c7 Merge branch 'dev' into 1511-occupancy-heat-map-tooltip 2025-05-14 12:05:34 +03:00
a9d6c6f4ee 1511-occupancy-heat-map-tooltip. 2025-05-14 12:03:47 +03:00
53 changed files with 1543 additions and 996 deletions

View File

@ -10,6 +10,7 @@
analyzer: analyzer:
errors: errors:
constant_identifier_names: ignore constant_identifier_names: ignore
overridden_fields: ignore
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
linter: linter:

View File

@ -1,27 +1,66 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class PhasesEnergyConsumption extends Equatable { class PhasesEnergyConsumption extends Equatable {
final int month; final String uuid;
final double phaseA; final DateTime createdAt;
final double phaseB; final DateTime updatedAt;
final double phaseC; final String deviceUuid;
final DateTime date;
final double energyConsumedKw;
final double energyConsumedA;
final double energyConsumedB;
final double energyConsumedC;
const PhasesEnergyConsumption({ const PhasesEnergyConsumption({
required this.month, required this.uuid,
required this.phaseA, required this.createdAt,
required this.phaseB, required this.updatedAt,
required this.phaseC, required this.deviceUuid,
required this.date,
required this.energyConsumedKw,
required this.energyConsumedA,
required this.energyConsumedB,
required this.energyConsumedC,
}); });
@override @override
List<Object?> get props => [month, phaseA, phaseB, phaseC]; List<Object?> get props => [
uuid,
createdAt,
updatedAt,
deviceUuid,
date,
energyConsumedKw,
energyConsumedA,
energyConsumedB,
energyConsumedC,
];
factory PhasesEnergyConsumption.fromJson(Map<String, dynamic> json) { factory PhasesEnergyConsumption.fromJson(Map<String, dynamic> json) {
return PhasesEnergyConsumption( return PhasesEnergyConsumption(
month: json['month'] as int, uuid: json['uuid'] as String,
phaseA: (json['phaseA'] as num).toDouble(), createdAt: DateTime.parse(json['createdAt'] as String),
phaseB: (json['phaseB'] as num).toDouble(), updatedAt: DateTime.parse(json['updatedAt'] as String),
phaseC: (json['phaseC'] as num).toDouble(), deviceUuid: json['deviceUuid'] as String,
date: DateTime.parse(json['date'] as String),
energyConsumedKw: double.parse(json['energyConsumedKw']),
energyConsumedA: double.parse(json['energyConsumedA']),
energyConsumedB: double.parse(json['energyConsumedB']),
energyConsumedC: double.parse(json['energyConsumedC']),
); );
} }
Map<String, dynamic> toJson() {
return {
'uuid': uuid,
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
'deviceUuid': deviceUuid,
'date': date.toIso8601String().split('T')[0],
'energyConsumedKw': energyConsumedKw.toString(),
'energyConsumedA': energyConsumedA.toString(),
'energyConsumedB': energyConsumedB.toString(),
'energyConsumedC': energyConsumedC.toString(),
};
}
} }

View File

@ -15,8 +15,8 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_he
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart'; import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart'; import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart'; import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart'; import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart'; import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart';
import 'package:syncrow_web/pages/analytics/services/occupacy/fake_occupacy_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupacy/fake_occupacy_service.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_service.dart';
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart'; import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart';
@ -57,12 +57,12 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
), ),
BlocProvider( BlocProvider(
create: (context) => EnergyConsumptionByPhasesBloc( create: (context) => EnergyConsumptionByPhasesBloc(
FakeEnergyConsumptionByPhasesService(), RemoteEnergyConsumptionByPhasesService(_httpService),
), ),
), ),
BlocProvider( BlocProvider(
create: (context) => EnergyConsumptionPerDeviceBloc( create: (context) => EnergyConsumptionPerDeviceBloc(
FakeEnergyConsumptionPerDeviceService(), RemoteEnergyConsumptionPerDeviceService(_httpService),
), ),
), ),
BlocProvider( BlocProvider(

View File

@ -14,7 +14,6 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final spaceTreeState = context.read<SpaceTreeBloc>().state;
return BlocBuilder<AnalyticsTabBloc, AnalyticsPageTab>( return BlocBuilder<AnalyticsTabBloc, AnalyticsPageTab>(
buildWhen: (previous, current) => previous != current, buildWhen: (previous, current) => previous != current,
builder: (context, selectedTab) => Column( builder: (context, selectedTab) => Column(
@ -68,7 +67,12 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
context.read<AnalyticsDatePickerBloc>().add( context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent(montlyDate: value), UpdateAnalyticsDatePickerEvent(montlyDate: value),
); );
FetchEnergyManagementDataHelper.loadEnergyManagementData(
final spaceTreeState =
context.read<SpaceTreeBloc>().state;
if (spaceTreeState.selectedSpaces.isNotEmpty) {
FetchEnergyManagementDataHelper
.loadEnergyManagementData(
context, context,
selectedDate: value, selectedDate: value,
communityId: communityId:
@ -77,6 +81,7 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
spaceId: spaceId:
spaceTreeState.selectedSpaces.firstOrNull ?? '', spaceTreeState.selectedSpaces.firstOrNull ?? '',
); );
}
}, },
selectedDate: context selectedDate: context
.watch<AnalyticsDatePickerBloc>() .watch<AnalyticsDatePickerBloc>()

View File

@ -45,7 +45,7 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Dialog( return Dialog(
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: ColorsManager.whiteColors,
child: Container( child: Container(
padding: const EdgeInsetsDirectional.all(20), padding: const EdgeInsetsDirectional.all(20),
width: 320, width: 320,
@ -121,6 +121,7 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
} }
Row _buildYearSelector() { Row _buildYearSelector() {
final currentYear = DateTime.now().year;
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -134,17 +135,35 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
), ),
const Spacer(), const Spacer(),
IconButton( IconButton(
onPressed: () => setState(() => _currentYear = _currentYear - 1), onPressed: () {
setState(() {
_currentYear = _currentYear - 1;
});
},
icon: const Icon( icon: const Icon(
Icons.chevron_left, Icons.chevron_left,
color: ColorsManager.grey700, color: ColorsManager.grey700,
), ),
), ),
IconButton( IconButton(
onPressed: () => setState(() => _currentYear = _currentYear + 1), onPressed: _currentYear < currentYear
icon: const Icon( ? () {
setState(() {
_currentYear = _currentYear + 1;
// Clear selected month if it becomes invalid in the new year
if (_currentYear == currentYear &&
_selectedMonth != null &&
_selectedMonth! > DateTime.now().month - 1) {
_selectedMonth = null;
}
});
}
: null,
icon: Icon(
Icons.chevron_right, Icons.chevron_right,
color: ColorsManager.grey700, color: _currentYear < currentYear
? ColorsManager.grey700
: ColorsManager.grey700.withValues(alpha: 0.3),
), ),
), ),
], ],
@ -152,6 +171,9 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
} }
Widget _buildMonthsGrid() { Widget _buildMonthsGrid() {
final currentDate = DateTime.now();
final isCurrentYear = _currentYear == currentDate.year;
return GridView.builder( return GridView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: 12, itemCount: 12,
@ -165,13 +187,17 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
), ),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final isSelected = _selectedMonth == index; final isSelected = _selectedMonth == index;
final isFutureMonth = isCurrentYear && index > currentDate.month - 1;
return InkWell( return InkWell(
onTap: () => setState(() => _selectedMonth = index), onTap: isFutureMonth ? null : () => setState(() => _selectedMonth = index),
child: Container( child: Container(
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected color: isSelected
? ColorsManager.vividBlue.withValues(alpha: 0.7) ? ColorsManager.vividBlue.withValues(alpha: 0.7)
: isFutureMonth
? ColorsManager.grey700.withValues(alpha: 0.1)
: const Color(0xFFEDF2F7), : const Color(0xFFEDF2F7),
borderRadius: borderRadius:
isSelected ? BorderRadius.circular(15) : BorderRadius.zero, isSelected ? BorderRadius.circular(15) : BorderRadius.zero,
@ -182,6 +208,8 @@ class _MonthPickerWidgetState extends State<MonthPickerWidget> {
fontSize: 12, fontSize: 12,
color: isSelected color: isSelected
? ColorsManager.whiteColors ? ColorsManager.whiteColors
: isFutureMonth
? ColorsManager.blackColor.withValues(alpha: 0.3)
: ColorsManager.blackColor.withValues(alpha: 0.8), : ColorsManager.blackColor.withValues(alpha: 0.8),
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),

View File

@ -20,9 +20,9 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
late int _currentYear; late int _currentYear;
static final years = List.generate( static final years = List.generate(
DateTime.now().year - 2020 + 1, DateTime.now().year - (DateTime.now().year - 5) + 1,
(index) => (2020 + index), (index) => (2020 + index),
); ).where((year) => year <= DateTime.now().year).toList();
@override @override
void initState() { void initState() {
@ -33,7 +33,7 @@ class _YearPickerWidgetState extends State<YearPickerWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Dialog( return Dialog(
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: ColorsManager.whiteColors,
child: Container( child: Container(
padding: const EdgeInsetsDirectional.all(20), padding: const EdgeInsetsDirectional.all(20),
width: 320, width: 320,

View File

@ -1,20 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
abstract final class EnergyConsumptionByPhasesChartHelper {
const EnergyConsumptionByPhasesChartHelper._();
static const fakeData = <PhasesEnergyConsumption>[
PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400),
PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500),
PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600),
PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100),
PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500),
PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400),
PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400),
PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100),
PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200),
PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50),
PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130),
PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 80, phaseC: 100),
];
}

View File

@ -20,7 +20,8 @@ abstract final class EnergyManagementChartsHelper {
interval: 1, interval: 1,
reservedSize: 32, reservedSize: 32,
showTitles: true, showTitles: true,
maxIncluded: true, maxIncluded: false,
minIncluded: false,
getTitlesWidget: (value, meta) => Padding( getTitlesWidget: (value, meta) => Padding(
padding: const EdgeInsetsDirectional.only(top: 20.0), padding: const EdgeInsetsDirectional.only(top: 20.0),
child: Text( child: Text(
@ -36,7 +37,8 @@ abstract final class EnergyManagementChartsHelper {
leftTitles: AxisTitles( leftTitles: AxisTitles(
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: true, showTitles: true,
maxIncluded: true, maxIncluded: false,
minIncluded: false,
interval: leftTitlesInterval, interval: leftTitlesInterval,
reservedSize: 110, reservedSize: 110,
getTitlesWidget: (value, meta) => Padding( getTitlesWidget: (value, meta) => Padding(

View File

@ -34,26 +34,44 @@ abstract final class FetchEnergyManagementDataHelper {
final datePickerState = context.read<AnalyticsDatePickerBloc>().state; final datePickerState = context.read<AnalyticsDatePickerBloc>().state;
final selectedDate0 = selectedDate ?? datePickerState.monthlyDate; final selectedDate0 = selectedDate ?? datePickerState.monthlyDate;
loadAnalyticsDevices(context, communityUuid: communityId, spaceUuid: spaceId); loadAnalyticsDevices(
context,
communityUuid: communityId,
spaceUuid: spaceId,
selectedDate: selectedDate0,
);
loadTotalEnergyConsumption( loadTotalEnergyConsumption(
context, context,
selectedDate: selectedDate0, selectedDate: selectedDate0,
communityId: communityId, communityId: communityId,
spaceId: spaceId, spaceId: spaceId,
); );
loadEnergyConsumptionByPhases(context, selectedDate: selectedDate); final selectedDevice = getSelectedDevice(context);
loadEnergyConsumptionPerDevice(context); if (selectedDevice case final AnalyticsDevice device) {
loadEnergyConsumptionByPhases(
context,
powerClampUuid: device.uuid,
selectedDate: selectedDate0,
);
}
loadEnergyConsumptionPerDevice(
context,
communityId: communityId,
spaceId: spaceId,
selectedDate: selectedDate0,
);
loadRealtimeDeviceChanges(context); loadRealtimeDeviceChanges(context);
loadPowerClampInfo(context); loadPowerClampInfo(context);
} }
static void loadEnergyConsumptionByPhases( static void loadEnergyConsumptionByPhases(
BuildContext context, { BuildContext context, {
required String powerClampUuid,
DateTime? selectedDate, DateTime? selectedDate,
}) { }) {
final param = GetEnergyConsumptionByPhasesParam( final param = GetEnergyConsumptionByPhasesParam(
startDate: selectedDate, date: selectedDate,
spaceId: '', powerClampUuid: powerClampUuid,
); );
context.read<EnergyConsumptionByPhasesBloc>().add( context.read<EnergyConsumptionByPhasesBloc>().add(
LoadEnergyConsumptionByPhasesEvent(param: param), LoadEnergyConsumptionByPhasesEvent(param: param),
@ -76,10 +94,19 @@ abstract final class FetchEnergyManagementDataHelper {
); );
} }
static void loadEnergyConsumptionPerDevice(BuildContext context) { static void loadEnergyConsumptionPerDevice(
const param = GetEnergyConsumptionPerDeviceParam(); BuildContext context, {
DateTime? selectedDate,
required String communityId,
required String spaceId,
}) {
final param = GetEnergyConsumptionPerDeviceParam(
spaceId: spaceId,
communityId: communityId,
monthDate: selectedDate,
);
context.read<EnergyConsumptionPerDeviceBloc>().add( context.read<EnergyConsumptionPerDeviceBloc>().add(
const LoadEnergyConsumptionPerDeviceEvent(param), LoadEnergyConsumptionPerDeviceEvent(param),
); );
} }
@ -107,6 +134,7 @@ abstract final class FetchEnergyManagementDataHelper {
BuildContext context, { BuildContext context, {
required String communityUuid, required String communityUuid,
required String spaceUuid, required String spaceUuid,
required DateTime selectedDate,
}) { }) {
context.read<AnalyticsDevicesBloc>().add( context.read<AnalyticsDevicesBloc>().add(
LoadAnalyticsDevicesEvent( LoadAnalyticsDevicesEvent(
@ -114,6 +142,11 @@ abstract final class FetchEnergyManagementDataHelper {
context.read<PowerClampInfoBloc>().add( context.read<PowerClampInfoBloc>().add(
LoadPowerClampInfoEvent(device.uuid), LoadPowerClampInfoEvent(device.uuid),
); );
loadEnergyConsumptionByPhases(
context,
powerClampUuid: device.uuid,
selectedDate: selectedDate,
);
context.read<RealtimeDeviceChangesBloc>().add( context.read<RealtimeDeviceChangesBloc>().add(
RealtimeDeviceChangesStarted(device.uuid), RealtimeDeviceChangesStarted(device.uuid),
); );

View File

@ -1,6 +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/get_month_name_from_int.dart'; import 'package:intl/intl.dart';
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart'; import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.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';
@ -18,7 +18,10 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BarChart( return BarChart(
BarChartData( BarChartData(
gridData: EnergyManagementChartsHelper.gridData(), gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true,
horizontalInterval: 250,
),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
barTouchData: _barTouchData(context), barTouchData: _barTouchData(context),
titlesData: _titlesData(context), titlesData: _titlesData(context),
@ -31,21 +34,25 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
barRods: [ barRods: [
BarChartRodData( BarChartRodData(
color: ColorsManager.vividBlue.withValues(alpha: 0.1), color: ColorsManager.vividBlue.withValues(alpha: 0.1),
toY: data.phaseA + data.phaseB + data.phaseC, toY: data.energyConsumedA +
data.energyConsumedB +
data.energyConsumedC,
rodStackItems: [ rodStackItems: [
BarChartRodStackItem( BarChartRodStackItem(
0, 0,
data.phaseA, data.energyConsumedA,
ColorsManager.vividBlue.withValues(alpha: 0.8), ColorsManager.vividBlue.withValues(alpha: 0.8),
), ),
BarChartRodStackItem( BarChartRodStackItem(
data.phaseA, data.energyConsumedA,
data.phaseA + data.phaseB, data.energyConsumedA + data.energyConsumedB,
ColorsManager.vividBlue.withValues(alpha: 0.4), ColorsManager.vividBlue.withValues(alpha: 0.4),
), ),
BarChartRodStackItem( BarChartRodStackItem(
data.phaseA + data.phaseB, data.energyConsumedA + data.energyConsumedB,
data.phaseA + data.phaseB + data.phaseC, data.energyConsumedA +
data.energyConsumedB +
data.energyConsumedC,
ColorsManager.vividBlue.withValues(alpha: 0.15), ColorsManager.vividBlue.withValues(alpha: 0.15),
), ),
], ],
@ -91,18 +98,27 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
}) { }) {
final data = energyData; final data = energyData;
final month = data[group.x.toInt()].month.getMonthName; final date = DateFormat('dd/MM/yyyy').format(data[group.x.toInt()].date);
final phaseA = data[group.x.toInt()].phaseA; final phaseA = data[group.x.toInt()].energyConsumedA;
final phaseB = data[group.x.toInt()].phaseB; final phaseB = data[group.x.toInt()].energyConsumedB;
final phaseC = data[group.x.toInt()].phaseC; final phaseC = data[group.x.toInt()].energyConsumedC;
final total = data[group.x.toInt()].energyConsumedKw;
return BarTooltipItem( return BarTooltipItem(
'$month\n', '$date\n',
context.textTheme.bodyMedium!.copyWith( context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
fontSize: 14, fontSize: 14,
), ),
textAlign: TextAlign.start,
children: [ children: [
TextSpan(
text: 'Total: $total\n',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontSize: 12,
),
),
TextSpan( TextSpan(
text: 'Phase A: $phaseA\n', text: 'Phase A: $phaseA\n',
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(
@ -144,7 +160,7 @@ class EnergyConsumptionByPhasesChart extends StatelessWidget {
sideTitles: SideTitles( sideTitles: SideTitles(
showTitles: true, showTitles: true,
getTitlesWidget: (value, _) { getTitlesWidget: (value, _) {
final month = energyData[value.toInt()].month.getMonthName; final month = DateFormat('dd/MM').format(energyData[value.toInt()].date);
return FittedBox( return FittedBox(
alignment: AlignmentDirectional.bottomCenter, alignment: AlignmentDirectional.bottomCenter,
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,

View File

@ -16,7 +16,10 @@ class EnergyConsumptionPerDeviceChart extends StatelessWidget {
context, context,
leftTitlesInterval: 250, leftTitlesInterval: 250,
), ),
gridData: EnergyManagementChartsHelper.gridData(), gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true,
horizontalInterval: 250,
),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: EnergyManagementChartsHelper.lineTouchData(), lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
lineBarsData: chartData.map((e) { lineBarsData: chartData.map((e) {

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart'; import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
@ -132,6 +133,12 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
alignment: AlignmentDirectional.centerEnd, alignment: AlignmentDirectional.centerEnd,
child: AnalyticsDeviceDropdown( child: AnalyticsDeviceDropdown(
onChanged: (value) { onChanged: (value) {
FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases(
context,
powerClampUuid: value.uuid,
selectedDate:
context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
);
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges( FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
context, context,
deviceUuid: value.uuid, deviceUuid: value.uuid,

View File

@ -4,15 +4,6 @@ import 'package:syncrow_web/pages/analytics/models/energy_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/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
// energy_consumption_chart will return id, name and consumption
const phasesJson = {
"1": {
"phaseOne": 1000,
"phaseTwo": 2000,
"phaseThree": 3000,
}
};
class TotalEnergyConsumptionChart extends StatelessWidget { class TotalEnergyConsumptionChart extends StatelessWidget {
const TotalEnergyConsumptionChart({required this.chartData, super.key}); const TotalEnergyConsumptionChart({required this.chartData, super.key});
@ -23,8 +14,14 @@ class TotalEnergyConsumptionChart extends StatelessWidget {
return Expanded( return Expanded(
child: LineChart( child: LineChart(
LineChartData( LineChartData(
titlesData: EnergyManagementChartsHelper.titlesData(context), titlesData: EnergyManagementChartsHelper.titlesData(
gridData: EnergyManagementChartsHelper.gridData(), context,
leftTitlesInterval: 250,
),
gridData: EnergyManagementChartsHelper.gridData().copyWith(
checkToShowHorizontalLine: (value) => true,
horizontalInterval: 250,
),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
lineTouchData: EnergyManagementChartsHelper.lineTouchData(), lineTouchData: EnergyManagementChartsHelper.lineTouchData(),
lineBarsData: _lineBarsData, lineBarsData: _lineBarsData,

View File

@ -28,25 +28,13 @@ abstract final class FetchOccupancyDataHelper {
loadAnalyticsDevices(context, communityUuid: communityId, spaceUuid: spaceId); loadAnalyticsDevices(context, communityUuid: communityId, spaceUuid: spaceId);
final selectedDevice = context.read<AnalyticsDevicesBloc>().state.selectedDevice; final selectedDevice = context.read<AnalyticsDevicesBloc>().state.selectedDevice;
context.read<OccupancyBloc>().add( loadOccupancyChartData(
LoadOccupancyEvent( context,
GetOccupancyParam(
monthDate:
'${datePickerState.monthlyDate.year}-${datePickerState.monthlyDate.month}',
spaceUuid: spaceId,
communityUuid: communityId, communityUuid: communityId,
),
),
);
context.read<OccupancyHeatMapBloc>().add(
LoadOccupancyHeatMapEvent(
GetOccupancyHeatMapParam(
spaceUuid: spaceId, spaceUuid: spaceId,
year: datePickerState.yearlyDate, date: datePickerState.monthlyDate,
),
),
); );
loadHeatMapData(context, spaceUuid: spaceId, year: datePickerState.yearlyDate);
if (selectedDevice case final AnalyticsDevice device) { if (selectedDevice case final AnalyticsDevice device) {
context.read<RealtimeDeviceChangesBloc>() context.read<RealtimeDeviceChangesBloc>()
@ -57,6 +45,35 @@ abstract final class FetchOccupancyDataHelper {
} }
} }
static void loadHeatMapData(
BuildContext context, {
required String spaceUuid,
required DateTime year,
}) {
context.read<OccupancyHeatMapBloc>().add(
LoadOccupancyHeatMapEvent(
GetOccupancyHeatMapParam(spaceUuid: spaceUuid, year: year),
),
);
}
static void loadOccupancyChartData(
BuildContext context, {
required String communityUuid,
required String spaceUuid,
required DateTime date,
}) {
context.read<OccupancyBloc>().add(
LoadOccupancyEvent(
GetOccupancyParam(
monthDate: '${date.year}-${date.month}',
spaceUuid: spaceUuid,
communityUuid: communityUuid,
),
),
);
}
static void loadAnalyticsDevices( static void loadAnalyticsDevices(
BuildContext context, { BuildContext context, {
required String communityUuid, required String communityUuid,

View File

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class HeatMapTooltip extends StatelessWidget {
const HeatMapTooltip({
required this.date,
required this.value,
super.key,
});
final DateTime date;
final int value;
@override
Widget build(BuildContext context) {
return FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.topStart,
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: ColorsManager.grey700,
borderRadius: BorderRadius.circular(3),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
DateFormat('MMM d, yyyy').format(date),
style: context.textTheme.bodySmall?.copyWith(
fontSize: 12,
fontWeight: FontWeight.w700,
color: ColorsManager.whiteColors,
),
),
const Divider(height: 2, thickness: 1),
Text(
'$value Occupants',
style: context.textTheme.bodySmall?.copyWith(
fontSize: 10,
fontWeight: FontWeight.w500,
color: ColorsManager.whiteColors,
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,108 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/heat_map_tooltip.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_painter.dart';
class InteractiveHeatMap extends StatefulWidget {
const InteractiveHeatMap({
required this.items,
required this.maxValue,
required this.cellSize,
super.key,
});
final List<OccupancyPaintItem> items;
final int maxValue;
final double cellSize;
@override
State<InteractiveHeatMap> createState() => _InteractiveHeatMapState();
}
class _InteractiveHeatMapState extends State<InteractiveHeatMap> {
OccupancyPaintItem? _hoveredItem;
OverlayEntry? _overlayEntry;
final LayerLink _layerLink = LayerLink();
@override
void dispose() {
_removeOverlay();
_overlayEntry?.dispose();
super.dispose();
}
void _removeOverlay() {
_overlayEntry?.remove();
_overlayEntry = null;
}
void _showTooltip(OccupancyPaintItem item, Offset localPosition) {
_removeOverlay();
final column = item.index ~/ 7;
final row = item.index % 7;
final x = column * widget.cellSize;
final y = row * widget.cellSize;
_overlayEntry = OverlayEntry(
builder: (context) => Positioned(
child: CompositedTransformFollower(
link: _layerLink,
offset: Offset(x + widget.cellSize, y),
child: Material(
color: Colors.transparent,
child: Transform.translate(
offset: Offset(-(widget.cellSize * 2.5), -50),
child: HeatMapTooltip(date: item.date, value: item.value),
),
),
),
),
);
Overlay.of(context).insert(_overlayEntry!);
}
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: _layerLink,
child: MouseRegion(
onHover: (event) {
final column = event.localPosition.dx ~/ widget.cellSize;
final row = event.localPosition.dy ~/ widget.cellSize;
final index = column * 7 + row;
if (index >= 0 && index < widget.items.length) {
final item = widget.items[index];
if (_hoveredItem != item) {
setState(() => _hoveredItem = item);
_showTooltip(item, event.localPosition);
}
} else {
_removeOverlay();
setState(() => _hoveredItem = null);
}
},
onExit: (_) {
_removeOverlay();
setState(() => _hoveredItem = null);
},
child: CustomPaint(
isComplex: true,
size: _painterSize,
painter: OccupancyPainter(
items: widget.items,
maxValue: widget.maxValue,
hoveredItem: _hoveredItem,
),
),
),
);
}
Size get _painterSize {
final height = 7 * widget.cellSize;
final width = widget.items.length ~/ 7 * widget.cellSize;
return Size(width, height);
}
}

View File

@ -23,7 +23,14 @@ class OccupancyChart extends StatelessWidget {
), ),
borderData: EnergyManagementChartsHelper.borderData(), borderData: EnergyManagementChartsHelper.borderData(),
barTouchData: _barTouchData(context), barTouchData: _barTouchData(context),
titlesData: _titlesData(context), titlesData: _titlesData(context).copyWith(
leftTitles: _titlesData(context).leftTitles.copyWith(
sideTitles: _titlesData(context).leftTitles.sideTitles.copyWith(
maxIncluded: true,
minIncluded: true,
),
),
),
barGroups: List.generate(chartData.length, (index) { barGroups: List.generate(chartData.length, (index) {
final actual = chartData[index]; final actual = chartData[index];
return BarChartGroupData( return BarChartGroupData(

View File

@ -47,11 +47,17 @@ class OccupancyChartBox extends StatelessWidget {
context.read<AnalyticsDatePickerBloc>().add( context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent(montlyDate: value), UpdateAnalyticsDatePickerEvent(montlyDate: value),
); );
FetchOccupancyDataHelper.loadOccupancyData( if (spaceTreeState.selectedSpaces.isNotEmpty) {
FetchOccupancyDataHelper.loadOccupancyChartData(
context, context,
communityId: spaceTreeState.selectedCommunities.firstOrNull ?? '', communityUuid:
spaceId: spaceTreeState.selectedSpaces.firstOrNull ?? '', spaceTreeState.selectedCommunities.firstOrNull ??
'',
spaceUuid:
spaceTreeState.selectedSpaces.firstOrNull ?? '',
date: value,
); );
}
}, },
selectedDate: context selectedDate: context
.watch<AnalyticsDatePickerBloc>() .watch<AnalyticsDatePickerBloc>()

View File

@ -1,6 +1,7 @@
import 'dart:math' as math show max; import 'dart:math' as math show max;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/interactive_heat_map.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_days.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_days.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_gradient.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_gradient.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_months.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_heat_map_months.dart';
@ -28,7 +29,7 @@ class OccupancyHeatMap extends StatelessWidget {
return List.generate(_totalWeeks * 7, (index) { return List.generate(_totalWeeks * 7, (index) {
final date = startDate.add(Duration(days: index)); final date = startDate.add(Duration(days: index));
final value = heatMapData[date] ?? 0; final value = heatMapData[date] ?? 0;
return OccupancyPaintItem(index: index, value: value); return OccupancyPaintItem(index: index, value: value, date: date);
}); });
} }
@ -58,15 +59,13 @@ class OccupancyHeatMap extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
const OccupancyHeatMapDays(cellSize: _cellSize), const OccupancyHeatMapDays(cellSize: _cellSize),
CustomPaint( SizedBox(
size: const Size(_totalWeeks * _cellSize, 7 * _cellSize), width: _totalWeeks * _cellSize,
child: CustomPaint( height: 7 * _cellSize,
isComplex: true, child: InteractiveHeatMap(
size: const Size(_totalWeeks * _cellSize, 7 * _cellSize),
painter: OccupancyPainter(
items: paintItems, items: paintItems,
maxValue: _maxValue, maxValue: _maxValue,
), cellSize: _cellSize,
), ),
), ),
], ],

View File

@ -47,12 +47,14 @@ class OccupancyHeatMapBox extends StatelessWidget {
context.read<AnalyticsDatePickerBloc>().add( context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent(yearlyDate: value), UpdateAnalyticsDatePickerEvent(yearlyDate: value),
); );
FetchOccupancyDataHelper.loadOccupancyData( if (spaceTreeState.selectedSpaces.isNotEmpty) {
FetchOccupancyDataHelper.loadHeatMapData(
context, context,
communityId: spaceUuid:
spaceTreeState.selectedCommunities.firstOrNull ?? '', spaceTreeState.selectedSpaces.firstOrNull ?? '',
spaceId: spaceTreeState.selectedSpaces.firstOrNull ?? '', year: value,
); );
}
}, },
datePickerType: DatePickerType.year, datePickerType: DatePickerType.year,
selectedDate: context selectedDate: context

View File

@ -4,18 +4,25 @@ import 'package:syncrow_web/utils/color_manager.dart';
class OccupancyPaintItem { class OccupancyPaintItem {
final int index; final int index;
final int value; final int value;
final DateTime date;
const OccupancyPaintItem({required this.index, required this.value}); const OccupancyPaintItem({
required this.index,
required this.value,
required this.date,
});
} }
class OccupancyPainter extends CustomPainter { class OccupancyPainter extends CustomPainter {
OccupancyPainter({ OccupancyPainter({
required this.items, required this.items,
required this.maxValue, required this.maxValue,
this.hoveredItem,
}); });
final List<OccupancyPaintItem> items; final List<OccupancyPaintItem> items;
final int maxValue; final int maxValue;
final OccupancyPaintItem? hoveredItem;
static const double cellSize = 16.0; static const double cellSize = 16.0;
@ -25,6 +32,10 @@ class OccupancyPainter extends CustomPainter {
final Paint borderPaint = Paint() final Paint borderPaint = Paint()
..color = ColorsManager.grayBorder.withValues(alpha: 0.4) ..color = ColorsManager.grayBorder.withValues(alpha: 0.4)
..style = PaintingStyle.stroke; ..style = PaintingStyle.stroke;
final Paint hoveredBorderPaint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 1.5;
for (final item in items) { for (final item in items) {
final column = item.index ~/ 7; final column = item.index ~/ 7;
@ -37,6 +48,10 @@ class OccupancyPainter extends CustomPainter {
final rect = Rect.fromLTWH(x, y, cellSize, cellSize); final rect = Rect.fromLTWH(x, y, cellSize, cellSize);
canvas.drawRect(rect, fillPaint); canvas.drawRect(rect, fillPaint);
// Highlight the hovered item
if (hoveredItem != null && hoveredItem!.index == item.index) {
canvas.drawRect(rect, hoveredBorderPaint);
} else {
_drawDashedLine( _drawDashedLine(
canvas, canvas,
Offset(x, y), Offset(x, y),
@ -51,8 +66,9 @@ class OccupancyPainter extends CustomPainter {
); );
canvas.drawLine(Offset(x, y), Offset(x, y + cellSize), borderPaint); canvas.drawLine(Offset(x, y), Offset(x, y + cellSize), borderPaint);
canvas.drawLine( canvas.drawLine(Offset(x + cellSize, y), Offset(x + cellSize, y + cellSize),
Offset(x + cellSize, y), Offset(x + cellSize, y + cellSize), borderPaint); borderPaint);
}
} }
} }
@ -80,5 +96,6 @@ class OccupancyPainter extends CustomPainter {
} }
@override @override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false; bool shouldRepaint(covariant OccupancyPainter oldDelegate) =>
oldDelegate.hoveredItem != hoveredItem;
} }

View File

@ -1,24 +1,20 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class GetEnergyConsumptionByPhasesParam extends Equatable { class GetEnergyConsumptionByPhasesParam extends Equatable {
final DateTime? startDate; final String powerClampUuid;
final DateTime? endDate; final DateTime? date;
final String? spaceId;
const GetEnergyConsumptionByPhasesParam({ const GetEnergyConsumptionByPhasesParam({
this.startDate, required this.powerClampUuid,
this.endDate, this.date,
this.spaceId,
}); });
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
'startDate': startDate?.toIso8601String(), 'monthDate': '${date?.year}-${date?.month.toString().padLeft(2, '0')}',
'endDate': endDate?.toIso8601String(),
'spaceId': spaceId,
}; };
} }
@override @override
List<Object?> get props => [startDate, endDate, spaceId]; List<Object?> get props => [powerClampUuid, date];
} }

View File

@ -1,3 +1,19 @@
class GetEnergyConsumptionPerDeviceParam { class GetEnergyConsumptionPerDeviceParam {
const GetEnergyConsumptionPerDeviceParam(); const GetEnergyConsumptionPerDeviceParam({
this.monthDate,
this.spaceId,
this.communityId,
});
final DateTime? monthDate;
final String? spaceId;
final String? communityId;
Map<String, dynamic> toJson() => {
'monthDate':
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
'communityUuid': communityId,
'groupByDevice': true,
};
} }

View File

@ -13,7 +13,7 @@ class GetTotalEnergyConsumptionParam {
return { return {
'monthDate': 'monthDate':
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}', '${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
if (communityId == null || communityId!.isEmpty) 'spaceUuid': spaceId, if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
'communityUuid': communityId, 'communityUuid': communityId,
'groupByDevice': false, 'groupByDevice': false,
}; };

View File

@ -39,7 +39,6 @@ class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService
path: path:
'/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.spaceUuid}/devices', '/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.spaceUuid}/devices',
queryParameters: { queryParameters: {
'requestType': param.requestType.name,
'communityUuid': param.communityUuid, 'communityUuid': param.communityUuid,
'spaceUuid': param.spaceUuid, 'spaceUuid': param.spaceUuid,
'productType': param.deviceTypes.first, 'productType': param.deviceTypes.first,

View File

@ -1,29 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart';
class FakeEnergyConsumptionByPhasesService
implements EnergyConsumptionByPhasesService {
@override
Future<List<PhasesEnergyConsumption>> load(
GetEnergyConsumptionByPhasesParam param,
) {
return Future.delayed(
const Duration(milliseconds: 500),
() => const [
PhasesEnergyConsumption(month: 1, phaseA: 200, phaseB: 300, phaseC: 400),
PhasesEnergyConsumption(month: 2, phaseA: 300, phaseB: 400, phaseC: 500),
PhasesEnergyConsumption(month: 3, phaseA: 400, phaseB: 500, phaseC: 600),
PhasesEnergyConsumption(month: 4, phaseA: 100, phaseB: 100, phaseC: 100),
PhasesEnergyConsumption(month: 5, phaseA: 300, phaseB: 400, phaseC: 500),
PhasesEnergyConsumption(month: 6, phaseA: 300, phaseB: 100, phaseC: 400),
PhasesEnergyConsumption(month: 7, phaseA: 300, phaseB: 100, phaseC: 400),
PhasesEnergyConsumption(month: 8, phaseA: 500, phaseB: 100, phaseC: 100),
PhasesEnergyConsumption(month: 9, phaseA: 500, phaseB: 100, phaseC: 200),
PhasesEnergyConsumption(month: 10, phaseA: 100, phaseB: 50, phaseC: 50),
PhasesEnergyConsumption(month: 11, phaseA: 600, phaseB: 750, phaseC: 130),
PhasesEnergyConsumption(month: 12, phaseA: 100, phaseB: 100, phaseC: 100),
],
);
}
}

View File

@ -15,8 +15,9 @@ final class RemoteEnergyConsumptionByPhasesService
) async { ) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
path: 'endpoint', path: '/power-clamp/${param.powerClampUuid}/historical',
showServerMessage: true, showServerMessage: true,
queryParameters: param.toJson(),
expectedResponseModel: (data) { expectedResponseModel: (data) {
final json = data as Map<String, dynamic>? ?? {}; final json = data as Map<String, dynamic>? ?? {};
final mappedData = json['data'] as List<dynamic>? ?? []; final mappedData = json['data'] as List<dynamic>? ?? [];
@ -28,7 +29,7 @@ final class RemoteEnergyConsumptionByPhasesService
); );
return response; return response;
} catch (e) { } catch (e) {
throw Exception('Failed to load energy consumption per device: $e'); throw Exception('Failed to load energy consumption per phase: $e');
} }
} }
} }

View File

@ -1,4 +1,6 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart'; import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart'; import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
@ -15,16 +17,10 @@ class RemoteEnergyConsumptionPerDeviceService
) async { ) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
path: 'endpoint', path: '/power-clamp/historical',
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (data) { queryParameters: param.toJson(),
final json = data as Map<String, dynamic>? ?? {}; expectedResponseModel: _EnergyConsumptionPerDeviceMapper.map,
final mappedData = json['data'] as List<dynamic>? ?? [];
return mappedData.map((e) {
final jsonData = e as Map<String, dynamic>;
return DeviceEnergyDataModel.fromJson(jsonData);
}).toList();
},
); );
return response; return response;
} catch (e) { } catch (e) {
@ -32,3 +28,33 @@ class RemoteEnergyConsumptionPerDeviceService
} }
} }
} }
abstract final class _EnergyConsumptionPerDeviceMapper {
const _EnergyConsumptionPerDeviceMapper._();
static List<DeviceEnergyDataModel> map(dynamic data) {
final json = data as Map<String, dynamic>? ?? {};
final mappedData = json['data'] as List<dynamic>? ?? [];
return mappedData.where((e) {
final deviceData = (e as Map<String, dynamic>)['data'] as List<dynamic>? ?? [];
return deviceData.isNotEmpty;
}).map((e) {
final deviceData = e as Map<String, dynamic>;
final energyData = deviceData['data'] as List<dynamic>;
return DeviceEnergyDataModel(
deviceId: deviceData['deviceUuid'] as String,
deviceName: deviceData['deviceName'] as String,
color: Color((DateTime.now().microsecondsSinceEpoch +
deviceData['deviceUuid'].hashCode) |
0xFF000000),
energy: energyData.map((data) {
final energyJson = data as Map<String, dynamic>;
return EnergyDataModel(
date: DateTime.parse(energyJson['date'] as String),
value: double.parse(energyJson['total_energy_consumed_kw'] as String),
);
}).toList(),
);
}).toList();
}
}

View File

@ -63,7 +63,8 @@ class _DynamicTableState extends State<DynamicTable> {
} }
} }
bool _compareListOfLists(List<List<dynamic>> oldList, List<List<dynamic>> newList) { bool _compareListOfLists(
List<List<dynamic>> oldList, List<List<dynamic>> newList) {
// Check if the old and new lists are the same // Check if the old and new lists are the same
if (oldList.length != newList.length) return false; if (oldList.length != newList.length) return false;
@ -132,14 +133,18 @@ class _DynamicTableState extends State<DynamicTable> {
children: [ children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(), if (widget.withCheckBox) _buildSelectAllCheckbox(),
...List.generate(widget.headers.length, (index) { ...List.generate(widget.headers.length, (index) {
return _buildTableHeaderCell(widget.headers[index], index); return _buildTableHeaderCell(
widget.headers[index], index);
}) })
//...widget.headers.map((header) => _buildTableHeaderCell(header)), //...widget.headers.map((header) => _buildTableHeaderCell(header)),
], ],
), ),
), ),
widget.isEmpty widget.isEmpty
? Column( ? SizedBox(
height: widget.size.height * 0.5,
width: widget.size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Row( Row(
@ -153,25 +158,35 @@ class _DynamicTableState extends State<DynamicTable> {
height: 15, height: 15,
), ),
Text( Text(
widget.tableName == 'AccessManagement' ? 'No Password ' : 'No Devices', widget.tableName == 'AccessManagement'
? 'No Password '
: 'No Devices',
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodySmall! .bodySmall!
.copyWith(color: ColorsManager.grayColor), .copyWith(
color:
ColorsManager.grayColor),
) )
], ],
), ),
], ],
), ),
], ],
),
) )
: Column( : Column(
children: List.generate(widget.data.length, (index) { children:
List.generate(widget.data.length, (index) {
final row = widget.data[index]; final row = widget.data[index];
return Row( return Row(
children: [ children: [
if (widget.withCheckBox) _buildRowCheckbox(index, widget.size.height * 0.08), if (widget.withCheckBox)
...row.map((cell) => _buildTableCell(cell.toString(), widget.size.height * 0.08)), _buildRowCheckbox(
index, widget.size.height * 0.08),
...row.map((cell) => _buildTableCell(
cell.toString(),
widget.size.height * 0.08)),
], ],
); );
}), }),
@ -196,7 +211,9 @@ class _DynamicTableState extends State<DynamicTable> {
), ),
child: Checkbox( child: Checkbox(
value: _selectAll, value: _selectAll,
onChanged: widget.withSelectAll && widget.data.isNotEmpty ? _toggleSelectAll : null, onChanged: widget.withSelectAll && widget.data.isNotEmpty
? _toggleSelectAll
: null,
), ),
); );
} }
@ -238,7 +255,9 @@ class _DynamicTableState extends State<DynamicTable> {
constraints: const BoxConstraints.expand(height: 40), constraints: const BoxConstraints.expand(height: 40),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: index == widget.headers.length - 1 ? 12 : 8.0, vertical: 4), padding: EdgeInsets.symmetric(
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
vertical: 4),
child: Text( child: Text(
title, title,
style: context.textTheme.titleSmall!.copyWith( style: context.textTheme.titleSmall!.copyWith(

View File

@ -97,7 +97,8 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
children: [ children: [
_buildInfoRow('Space Name:', _buildInfoRow('Space Name:',
device.spaces?.firstOrNull?.spaceName ?? 'N/A'), device.spaces?.firstOrNull?.spaceName ?? 'N/A'),
_buildInfoRow('Room:', device.subspace?.subspaceName ?? 'N/A'), _buildInfoRow(
'Sub space:', device.subspace?.subspaceName ?? 'N/A'),
], ],
), ),
TableRow( TableRow(

View File

@ -26,8 +26,10 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
functionCode: event.functionData.functionCode, functionCode: event.functionData.functionCode,
operationName: event.functionData.operationName, operationName: event.functionData.operationName,
value: event.functionData.value ?? existingData.value, value: event.functionData.value ?? existingData.value,
valueDescription: event.functionData.valueDescription ?? existingData.valueDescription, valueDescription: event.functionData.valueDescription ??
existingData.valueDescription,
condition: event.functionData.condition ?? existingData.condition, condition: event.functionData.condition ?? existingData.condition,
step: event.functionData.step ?? existingData.step,
); );
} else { } else {
functions.clear(); functions.clear();
@ -59,8 +61,10 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
); );
} }
FutureOr<void> _onSelectFunction(SelectFunction event, Emitter<FunctionBlocState> emit) { FutureOr<void> _onSelectFunction(
SelectFunction event, Emitter<FunctionBlocState> emit) {
emit(state.copyWith( emit(state.copyWith(
selectedFunction: event.functionCode, selectedOperationName: event.operationName)); selectedFunction: event.functionCode,
selectedOperationName: event.operationName));
} }
} }

View File

@ -14,6 +14,10 @@ abstract class ACFunction extends DeviceFunction<AcStatusModel> {
required super.operationName, required super.operationName,
required super.icon, required super.icon,
required this.type, required this.type,
super.step,
super.unit,
super.max,
super.min,
}); });
List<ACOperationalValue> getOperationalValues(); List<ACOperationalValue> getOperationalValues();
@ -75,26 +79,24 @@ class ModeFunction extends ACFunction {
} }
class TempSetFunction extends ACFunction { class TempSetFunction extends ACFunction {
final int min; TempSetFunction({
final int max; required super.deviceId,
final int step; required super.deviceName,
required super.type,
TempSetFunction( }) : super(
{required super.deviceId, required super.deviceName, required type})
: min = 160,
max = 300,
step = 1,
super(
code: 'temp_set', code: 'temp_set',
operationName: 'Set Temperature', operationName: 'Set Temperature',
icon: Assets.assetsTempreture, icon: Assets.assetsTempreture,
type: type, min: 200,
max: 300,
step: 1,
unit: "°C",
); );
@override @override
List<ACOperationalValue> getOperationalValues() { List<ACOperationalValue> getOperationalValues() {
List<ACOperationalValue> values = []; List<ACOperationalValue> values = [];
for (int temp = min; temp <= max; temp += step) { for (int temp = min!.toInt(); temp <= max!; temp += step!.toInt()) {
values.add(ACOperationalValue( values.add(ACOperationalValue(
icon: Assets.assetsTempreture, icon: Assets.assetsTempreture,
description: "${temp / 10}°C", description: "${temp / 10}°C",
@ -104,7 +106,6 @@ class TempSetFunction extends ACFunction {
return values; return values;
} }
} }
class LevelFunction extends ACFunction { class LevelFunction extends ACFunction {
LevelFunction( LevelFunction(
{required super.deviceId, required super.deviceName, required type}) {required super.deviceId, required super.deviceName, required type})
@ -166,9 +167,10 @@ class ChildLockFunction extends ACFunction {
} }
class CurrentTempFunction extends ACFunction { class CurrentTempFunction extends ACFunction {
final int min; final double min;
final int max; final double max;
final int step; final double step;
final String unit = "°C";
CurrentTempFunction( CurrentTempFunction(
{required super.deviceId, required super.deviceName, required type}) {required super.deviceId, required super.deviceName, required type})
@ -185,7 +187,7 @@ class CurrentTempFunction extends ACFunction {
@override @override
List<ACOperationalValue> getOperationalValues() { List<ACOperationalValue> getOperationalValues() {
List<ACOperationalValue> values = []; List<ACOperationalValue> values = [];
for (int temp = min; temp <= max; temp += step) { for (int temp = min.toInt(); temp <= max; temp += step.toInt()) {
values.add(ACOperationalValue( values.add(ACOperationalValue(
icon: Assets.currentTemp, icon: Assets.currentTemp,
description: "${temp / 10}°C", description: "${temp / 10}°C",

View File

@ -6,10 +6,12 @@ class CpsOperationalValue {
final String description; final String description;
final dynamic value; final dynamic value;
CpsOperationalValue({ CpsOperationalValue({
required this.icon, required this.icon,
required this.description, required this.description,
required this.value, required this.value,
}); });
} }
@ -94,9 +96,9 @@ final class CpsSensitivityFunction extends CpsFunctions {
icon: Assets.sensitivity, icon: Assets.sensitivity,
); );
final int min; final double min;
final int max; final double max;
final int step; final double step;
static const _images = <String>[ static const _images = <String>[
Assets.sensitivityFeature1, Assets.sensitivityFeature1,
@ -115,10 +117,10 @@ final class CpsSensitivityFunction extends CpsFunctions {
@override @override
List<CpsOperationalValue> getOperationalValues() { List<CpsOperationalValue> getOperationalValues() {
final values = <CpsOperationalValue>[]; final values = <CpsOperationalValue>[];
for (var value = min; value <= max; value += step) { for (var value = min; value <= max; value += step.toInt()) {
values.add( values.add(
CpsOperationalValue( CpsOperationalValue(
icon: _images[value], icon: _images[value.toInt()],
description: '$value', description: '$value',
value: value, value: value,
), ),
@ -142,9 +144,9 @@ final class CpsMovingSpeedFunction extends CpsFunctions {
icon: Assets.speedoMeter, icon: Assets.speedoMeter,
); );
final int min; final double min;
final int max; final double max;
final int step; final double step;
@override @override
List<CpsOperationalValue> getOperationalValues() { List<CpsOperationalValue> getOperationalValues() {
@ -173,9 +175,9 @@ final class CpsSpatialStaticValueFunction extends CpsFunctions {
icon: Assets.spatialStaticValue, icon: Assets.spatialStaticValue,
); );
final int min; final double min;
final int max; final double max;
final int step; final double step;
@override @override
List<CpsOperationalValue> getOperationalValues() { List<CpsOperationalValue> getOperationalValues() {
@ -204,9 +206,9 @@ final class CpsSpatialMotionValueFunction extends CpsFunctions {
icon: Assets.spatialMotionValue, icon: Assets.spatialMotionValue,
); );
final int min; final double min;
final int max; final double max;
final int step; final double step;
@override @override
List<CpsOperationalValue> getOperationalValues() { List<CpsOperationalValue> getOperationalValues() {
@ -375,9 +377,9 @@ final class CpsPresenceJudgementThrsholdFunction extends CpsFunctions {
icon: Assets.presenceJudgementThrshold, icon: Assets.presenceJudgementThrshold,
); );
final int min; final double min;
final int max; final double max;
final int step; final double step;
@override @override
List<CpsOperationalValue> getOperationalValues() { List<CpsOperationalValue> getOperationalValues() {
@ -406,9 +408,9 @@ final class CpsMotionAmplitudeTriggerThresholdFunction extends CpsFunctions {
icon: Assets.presenceJudgementThrshold, icon: Assets.presenceJudgementThrshold,
); );
final int min; final double min;
final int max; final double max;
final int step; final double step;
@override @override
List<CpsOperationalValue> getOperationalValues() { List<CpsOperationalValue> getOperationalValues() {

View File

@ -4,6 +4,11 @@ abstract class DeviceFunction<T> {
final String code; final String code;
final String operationName; final String operationName;
final String icon; final String icon;
final double? step;
final String? unit;
final double? max;
final double? min;
DeviceFunction({ DeviceFunction({
required this.deviceId, required this.deviceId,
@ -11,6 +16,10 @@ abstract class DeviceFunction<T> {
required this.code, required this.code,
required this.operationName, required this.operationName,
required this.icon, required this.icon,
this.step,
this.unit,
this.max,
this.min,
}); });
} }
@ -22,6 +31,10 @@ class DeviceFunctionData {
final dynamic value; final dynamic value;
final String? condition; final String? condition;
final String? valueDescription; final String? valueDescription;
final double? step;
final String? unit;
final double? max;
final double? min;
DeviceFunctionData({ DeviceFunctionData({
required this.entityId, required this.entityId,
@ -31,6 +44,10 @@ class DeviceFunctionData {
required this.value, required this.value,
this.condition, this.condition,
this.valueDescription, this.valueDescription,
this.step,
this.unit,
this.max,
this.min,
}); });
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -42,6 +59,10 @@ class DeviceFunctionData {
'value': value, 'value': value,
if (condition != null) 'condition': condition, if (condition != null) 'condition': condition,
if (valueDescription != null) 'valueDescription': valueDescription, if (valueDescription != null) 'valueDescription': valueDescription,
if (step != null) 'step': step,
if (unit != null) 'unit': unit,
if (max != null) 'max': max,
if (min != null) 'min': min,
}; };
} }
@ -54,6 +75,10 @@ class DeviceFunctionData {
value: json['value'], value: json['value'],
condition: json['condition'], condition: json['condition'],
valueDescription: json['valueDescription'], valueDescription: json['valueDescription'],
step: json['step']?.toDouble(),
unit: json['unit'],
max: json['max']?.toDouble(),
min: json['min']?.toDouble(),
); );
} }
@ -68,7 +93,11 @@ class DeviceFunctionData {
other.operationName == operationName && other.operationName == operationName &&
other.value == value && other.value == value &&
other.condition == condition && other.condition == condition &&
other.valueDescription == valueDescription; other.valueDescription == valueDescription &&
other.step == step &&
other.unit == unit &&
other.max == max &&
other.min == min;
} }
@override @override
@ -79,6 +108,10 @@ class DeviceFunctionData {
operationName.hashCode ^ operationName.hashCode ^
value.hashCode ^ value.hashCode ^
condition.hashCode ^ condition.hashCode ^
valueDescription.hashCode; valueDescription.hashCode ^
step.hashCode ^
unit.hashCode ^
max.hashCode ^
min.hashCode;
} }
} }

View File

@ -20,12 +20,11 @@ abstract class FlushFunctions
} }
class FlushPresenceDelayFunction extends FlushFunctions { class FlushPresenceDelayFunction extends FlushFunctions {
final int min;
FlushPresenceDelayFunction({ FlushPresenceDelayFunction({
required super.deviceId, required super.deviceId,
required super.deviceName, required super.deviceName,
required super.type, required super.type,
}) : min = 0, }) :
super( super(
code: FlushMountedPresenceSensorModel.codePresenceState, code: FlushMountedPresenceSensorModel.codePresenceState,
operationName: 'Presence State', operationName: 'Presence State',
@ -50,9 +49,9 @@ class FlushPresenceDelayFunction extends FlushFunctions {
} }
class FlushSensiReduceFunction extends FlushFunctions { class FlushSensiReduceFunction extends FlushFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
FlushSensiReduceFunction({ FlushSensiReduceFunction({
required super.deviceId, required super.deviceId,
@ -80,8 +79,8 @@ class FlushSensiReduceFunction extends FlushFunctions {
} }
class FlushNoneDelayFunction extends FlushFunctions { class FlushNoneDelayFunction extends FlushFunctions {
final int min; final double min;
final int max; final double max;
final String unit; final String unit;
FlushNoneDelayFunction({ FlushNoneDelayFunction({
@ -110,9 +109,9 @@ class FlushNoneDelayFunction extends FlushFunctions {
} }
class FlushIlluminanceFunction extends FlushFunctions { class FlushIlluminanceFunction extends FlushFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
FlushIlluminanceFunction({ FlushIlluminanceFunction({
required super.deviceId, required super.deviceId,
@ -130,7 +129,7 @@ class FlushIlluminanceFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
List<FlushOperationalValue> values = []; List<FlushOperationalValue> values = [];
for (int lux = min; lux <= max; lux += step) { for (int lux = min.toInt(); lux <= max; lux += step.toInt()) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.IlluminanceIcon, icon: Assets.IlluminanceIcon,
description: "$lux Lux", description: "$lux Lux",
@ -142,9 +141,9 @@ class FlushIlluminanceFunction extends FlushFunctions {
} }
class FlushOccurDistReduceFunction extends FlushFunctions { class FlushOccurDistReduceFunction extends FlushFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
FlushOccurDistReduceFunction({ FlushOccurDistReduceFunction({
required super.deviceId, required super.deviceId,
@ -173,9 +172,9 @@ class FlushOccurDistReduceFunction extends FlushFunctions {
// ==== then functions ==== // ==== then functions ====
class FlushSensitivityFunction extends FlushFunctions { class FlushSensitivityFunction extends FlushFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
FlushSensitivityFunction({ FlushSensitivityFunction({
required super.deviceId, required super.deviceId,
@ -203,9 +202,9 @@ class FlushSensitivityFunction extends FlushFunctions {
} }
class FlushNearDetectionFunction extends FlushFunctions { class FlushNearDetectionFunction extends FlushFunctions {
final int min; final double min;
final double max; final double max;
final int step; final double step;
final String unit; final String unit;
FlushNearDetectionFunction({ FlushNearDetectionFunction({
@ -225,7 +224,7 @@ class FlushNearDetectionFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) { for (var value = min.toDouble(); value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -237,9 +236,9 @@ class FlushNearDetectionFunction extends FlushFunctions {
} }
class FlushMaxDetectDistFunction extends FlushFunctions { class FlushMaxDetectDistFunction extends FlushFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
final String unit; final String unit;
FlushMaxDetectDistFunction({ FlushMaxDetectDistFunction({
@ -259,7 +258,7 @@ class FlushMaxDetectDistFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) { for (var value = min; value <= max; value += step.toInt()) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -271,9 +270,9 @@ class FlushMaxDetectDistFunction extends FlushFunctions {
} }
class FlushTargetConfirmTimeFunction extends FlushFunctions { class FlushTargetConfirmTimeFunction extends FlushFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
final String unit; final String unit;
FlushTargetConfirmTimeFunction({ FlushTargetConfirmTimeFunction({
@ -293,7 +292,7 @@ class FlushTargetConfirmTimeFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) { for (var value = min.toDouble(); value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -305,9 +304,9 @@ class FlushTargetConfirmTimeFunction extends FlushFunctions {
} }
class FlushDisappeDelayFunction extends FlushFunctions { class FlushDisappeDelayFunction extends FlushFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
final String unit; final String unit;
FlushDisappeDelayFunction({ FlushDisappeDelayFunction({
@ -327,7 +326,7 @@ class FlushDisappeDelayFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) { for (var value = min.toDouble(); value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -339,9 +338,9 @@ class FlushDisappeDelayFunction extends FlushFunctions {
} }
class FlushIndentLevelFunction extends FlushFunctions { class FlushIndentLevelFunction extends FlushFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
final String unit; final String unit;
FlushIndentLevelFunction({ FlushIndentLevelFunction({
@ -361,7 +360,7 @@ class FlushIndentLevelFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) { for (var value = min.toDouble(); value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -373,9 +372,9 @@ class FlushIndentLevelFunction extends FlushFunctions {
} }
class FlushTriggerLevelFunction extends FlushFunctions { class FlushTriggerLevelFunction extends FlushFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
final String unit; final String unit;
FlushTriggerLevelFunction({ FlushTriggerLevelFunction({
@ -395,7 +394,7 @@ class FlushTriggerLevelFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min; value <= max; value += step) { for (var value = min.toDouble(); value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',

View File

@ -20,18 +20,17 @@ abstract class WaterHeaterFunctions
} }
class WHRestartStatusFunction extends WaterHeaterFunctions { class WHRestartStatusFunction extends WaterHeaterFunctions {
final int min;
WHRestartStatusFunction({ WHRestartStatusFunction({
required super.deviceId, required super.deviceId,
required super.deviceName, required super.deviceName,
required super.type, required super.type,
}) : min = 0, }) : super(
super(
code: 'relay_status', code: 'relay_status',
operationName: 'Restart Status', operationName: 'Restart Status',
icon: Assets.refreshStatusIcon, icon: Assets.refreshStatusIcon,
); );
@override @override
List<WaterHeaterOperationalValue> getOperationalValues() { List<WaterHeaterOperationalValue> getOperationalValues() {
return [ return [
@ -55,13 +54,11 @@ class WHRestartStatusFunction extends WaterHeaterFunctions {
} }
class WHSwitchFunction extends WaterHeaterFunctions { class WHSwitchFunction extends WaterHeaterFunctions {
final int min;
WHSwitchFunction({ WHSwitchFunction({
required super.deviceId, required super.deviceId,
required super.deviceName, required super.deviceName,
required super.type, required super.type,
}) : min = 0, }) : super(
super(
code: 'switch_1', code: 'switch_1',
operationName: 'Switch', operationName: 'Switch',
icon: Assets.assetsAcPower, icon: Assets.assetsAcPower,
@ -104,12 +101,11 @@ class TimerConfirmTimeFunction extends WaterHeaterFunctions {
} }
class BacklightFunction extends WaterHeaterFunctions { class BacklightFunction extends WaterHeaterFunctions {
final int min;
BacklightFunction({ BacklightFunction({
required super.deviceId, required super.deviceId,
required super.deviceName, required super.deviceName,
required super.type, required super.type,
}) : min = 0, }) :
super( super(
code: 'switch_backlight', code: 'switch_backlight',
operationName: 'Backlight', operationName: 'Backlight',

View File

@ -13,6 +13,10 @@ abstract class WpsFunctions extends DeviceFunction<WallSensorModel> {
required super.operationName, required super.operationName,
required super.icon, required super.icon,
required this.type, required this.type,
super.step,
super.unit,
super.max,
super.min,
}); });
List<WpsOperationalValue> getOperationalValues(); List<WpsOperationalValue> getOperationalValues();
@ -20,9 +24,13 @@ abstract class WpsFunctions extends DeviceFunction<WallSensorModel> {
// For far_detection (75-600cm in 75cm steps) // For far_detection (75-600cm in 75cm steps)
class FarDetectionFunction extends WpsFunctions { class FarDetectionFunction extends WpsFunctions {
final int min;
final int max; final double min;
final int step; @override
final double max;
@override
final double step;
@override
final String unit; final String unit;
FarDetectionFunction( FarDetectionFunction(
@ -41,7 +49,7 @@ class FarDetectionFunction extends WpsFunctions {
@override @override
List<WpsOperationalValue> getOperationalValues() { List<WpsOperationalValue> getOperationalValues() {
final values = <WpsOperationalValue>[]; final values = <WpsOperationalValue>[];
for (var value = min; value <= max; value += step) { for (var value = min; value <= max; value += step.toInt()) {
values.add(WpsOperationalValue( values.add(WpsOperationalValue(
icon: Assets.currentDistanceIcon, icon: Assets.currentDistanceIcon,
description: '$value $unit', description: '$value $unit',
@ -54,9 +62,9 @@ class FarDetectionFunction extends WpsFunctions {
// For presence_time (0-65535 minutes) // For presence_time (0-65535 minutes)
class PresenceTimeFunction extends WpsFunctions { class PresenceTimeFunction extends WpsFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
final String unit; final String unit;
PresenceTimeFunction( PresenceTimeFunction(
@ -86,9 +94,9 @@ class PresenceTimeFunction extends WpsFunctions {
// For motion_sensitivity_value (1-5 levels) // For motion_sensitivity_value (1-5 levels)
class MotionSensitivityFunction extends WpsFunctions { class MotionSensitivityFunction extends WpsFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
MotionSensitivityFunction( MotionSensitivityFunction(
{required super.deviceId, required super.deviceName, required type}) {required super.deviceId, required super.deviceName, required type})
@ -116,9 +124,9 @@ class MotionSensitivityFunction extends WpsFunctions {
} }
class MotionLessSensitivityFunction extends WpsFunctions { class MotionLessSensitivityFunction extends WpsFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
MotionLessSensitivityFunction( MotionLessSensitivityFunction(
{required super.deviceId, required super.deviceName, required type}) {required super.deviceId, required super.deviceName, required type})
@ -171,8 +179,8 @@ class IndicatorFunction extends WpsFunctions {
} }
class NoOneTimeFunction extends WpsFunctions { class NoOneTimeFunction extends WpsFunctions {
final int min; final double min;
final int max; final double max;
final String unit; final String unit;
NoOneTimeFunction( NoOneTimeFunction(
@ -225,9 +233,9 @@ class PresenceStateFunction extends WpsFunctions {
} }
class CurrentDistanceFunction extends WpsFunctions { class CurrentDistanceFunction extends WpsFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
CurrentDistanceFunction( CurrentDistanceFunction(
{required super.deviceId, required super.deviceName, required type}) {required super.deviceId, required super.deviceName, required type})
@ -244,11 +252,10 @@ class CurrentDistanceFunction extends WpsFunctions {
@override @override
List<WpsOperationalValue> getOperationalValues() { List<WpsOperationalValue> getOperationalValues() {
List<WpsOperationalValue> values = []; List<WpsOperationalValue> values = [];
for (int cm = min; cm <= max; cm += step) { for (int cm = min.toInt(); cm <= max; cm += step.toInt()) {
values.add(WpsOperationalValue( values.add(WpsOperationalValue(
icon: Assets.assetsTempreture, icon: Assets.assetsTempreture,
description: "${cm}CM", description: "${cm}CM",
value: cm, value: cm,
)); ));
} }
@ -257,9 +264,9 @@ class CurrentDistanceFunction extends WpsFunctions {
} }
class IlluminanceValueFunction extends WpsFunctions { class IlluminanceValueFunction extends WpsFunctions {
final int min; final double min;
final int max; final double max;
final int step; final double step;
IlluminanceValueFunction({ IlluminanceValueFunction({
required super.deviceId, required super.deviceId,
@ -277,7 +284,7 @@ class IlluminanceValueFunction extends WpsFunctions {
@override @override
List<WpsOperationalValue> getOperationalValues() { List<WpsOperationalValue> getOperationalValues() {
List<WpsOperationalValue> values = []; List<WpsOperationalValue> values = [];
for (int lux = min; lux <= max; lux += step) { for (int lux = min.toInt(); lux <= max; lux += step.toInt()) {
values.add(WpsOperationalValue( values.add(WpsOperationalValue(
icon: Assets.IlluminanceIcon, icon: Assets.IlluminanceIcon,
description: "$lux Lux", description: "$lux Lux",

View File

@ -0,0 +1,297 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:syncrow_web/pages/routines/widgets/condition_toggle.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class CustomRoutinesTextbox extends StatefulWidget {
final String? currentCondition;
final String dialogType;
final (double, double) sliderRange;
final dynamic displayedValue;
final dynamic initialValue;
final void Function(String condition) onConditionChanged;
final void Function(double value) onTextChanged;
final String unit;
final double dividendOfRange;
final double stepIncreaseAmount;
final bool withSpecialChar;
const CustomRoutinesTextbox({
required this.dialogType,
required this.sliderRange,
required this.displayedValue,
required this.initialValue,
required this.onConditionChanged,
required this.onTextChanged,
required this.currentCondition,
required this.unit,
required this.dividendOfRange,
required this.stepIncreaseAmount,
required this.withSpecialChar,
super.key,
});
@override
State<CustomRoutinesTextbox> createState() => _CustomRoutinesTextboxState();
}
class _CustomRoutinesTextboxState extends State<CustomRoutinesTextbox> {
late final TextEditingController _controller;
bool hasError = false;
String? errorMessage;
int getDecimalPlaces(double step) {
String stepStr = step.toString();
if (stepStr.contains('.')) {
List<String> parts = stepStr.split('.');
String decimalPart = parts[1];
decimalPart = decimalPart.replaceAll(RegExp(r'0+$'), '');
return decimalPart.isEmpty ? 0 : decimalPart.length;
} else {
return 0;
}
}
@override
void initState() {
super.initState();
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
double initialValue;
if (widget.initialValue != null &&
widget.initialValue is num &&
(widget.initialValue as num) == 0) {
initialValue = 0.0;
} else {
initialValue = double.tryParse(widget.displayedValue) ?? 0.0;
}
_controller = TextEditingController(
text: initialValue.toStringAsFixed(decimalPlaces),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _validateInput(String value) {
final doubleValue = double.tryParse(value);
if (doubleValue == null) {
setState(() {
errorMessage = "Invalid number";
hasError = true;
});
return;
}
final min = widget.sliderRange.$1;
final max = widget.sliderRange.$2;
if (doubleValue < min) {
setState(() {
errorMessage = "Value must be at least $min";
hasError = true;
});
} else if (doubleValue > max) {
setState(() {
errorMessage = "Value must be at most $max";
hasError = true;
});
} else {
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
int factor = pow(10, decimalPlaces).toInt();
int scaledStep = (widget.stepIncreaseAmount * factor).round();
int scaledValue = (doubleValue * factor).round();
if (scaledValue % scaledStep != 0) {
setState(() {
errorMessage = "must be a multiple of ${widget.stepIncreaseAmount}";
hasError = true;
});
} else {
setState(() {
errorMessage = null;
hasError = false;
});
}
}
}
@override
void didUpdateWidget(CustomRoutinesTextbox oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.initialValue != oldWidget.initialValue) {
if (widget.initialValue != null &&
widget.initialValue is num &&
(widget.initialValue as num) == 0) {
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
_controller.text = 0.0.toStringAsFixed(decimalPlaces);
}
}
}
void _correctAndUpdateValue(String value) {
final doubleValue = double.tryParse(value) ?? 0.0;
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
double rounded = (doubleValue / widget.stepIncreaseAmount).round() *
widget.stepIncreaseAmount;
rounded = rounded.clamp(widget.sliderRange.$1, widget.sliderRange.$2);
rounded = double.parse(rounded.toStringAsFixed(decimalPlaces));
setState(() {
hasError = false;
errorMessage = null;
});
_controller.text = rounded.toStringAsFixed(decimalPlaces);
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _controller.text.length),
);
widget.onTextChanged(rounded);
}
@override
Widget build(BuildContext context) {
int decimalPlaces = getDecimalPlaces(widget.stepIncreaseAmount);
List<TextInputFormatter> formatters = [];
if (decimalPlaces == 0) {
formatters.add(FilteringTextInputFormatter.digitsOnly);
} else {
formatters.add(FilteringTextInputFormatter.allow(
RegExp(r'^\d*\.?\d{0,' + decimalPlaces.toString() + r'}$'),
));
}
formatters.add(RangeInputFormatter(
min: widget.sliderRange.$1,
max: widget.sliderRange.$2,
));
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (widget.dialogType == 'IF')
ConditionToggle(
currentCondition: widget.currentCondition,
onChanged: widget.onConditionChanged,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 2),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
'Step: ${widget.stepIncreaseAmount}',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.grayColor,
fontSize: 10,
fontWeight: FontWeight.w400,
),
),
],
),
),
Center(
child: Container(
width: 170,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFFF8F8F8),
borderRadius: BorderRadius.circular(20),
border: hasError
? Border.all(color: Colors.red, width: 1)
: Border.all(
color: ColorsManager.lightGrayBorderColor, width: 1),
boxShadow: [
BoxShadow(
color: ColorsManager.blackColor.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _controller,
style: context.textTheme.bodyLarge?.copyWith(
fontSize: 20,
fontWeight: FontWeight.bold,
color: ColorsManager.blackColor,
),
keyboardType: TextInputType.number,
inputFormatters: widget.withSpecialChar == true
? [FilteringTextInputFormatter.digitsOnly]
: null,
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.zero,
),
onChanged: _validateInput,
onFieldSubmitted: _correctAndUpdateValue,
onTapOutside: (_) =>
_correctAndUpdateValue(_controller.text),
),
),
const SizedBox(width: 12),
Text(
widget.unit,
style: context.textTheme.bodyMedium?.copyWith(
fontSize: 20,
fontWeight: FontWeight.bold,
color: ColorsManager.vividBlue,
),
),
],
),
),
),
if (errorMessage != null)
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: Text(
errorMessage!,
style: context.textTheme.bodySmall?.copyWith(
color: Colors.red,
fontSize: 10,
),
),
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Min. ${widget.sliderRange.$1.toInt()}${widget.unit}',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.grayColor,
fontSize: 10,
fontWeight: FontWeight.w400,
),
),
Text(
'Max. ${widget.sliderRange.$2.toInt()}${widget.unit}',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.grayColor,
fontSize: 10,
fontWeight: FontWeight.w400,
),
),
],
),
),
const SizedBox(height: 16),
],
);
}
}

View File

@ -7,6 +7,7 @@ import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/ac/ac_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
@ -76,7 +77,8 @@ class ACHelper {
context: context, context: context,
acFunctions: acFunctions, acFunctions: acFunctions,
device: device, device: device,
onFunctionSelected: (functionCode, operationName) { onFunctionSelected:
(functionCode, operationName) {
RoutineTapFunctionHelper.onTapFunction( RoutineTapFunctionHelper.onTapFunction(
context, context,
functionCode: functionCode, functionCode: functionCode,
@ -88,12 +90,7 @@ class ACHelper {
'temp_set', 'temp_set',
'temp_current', 'temp_current',
], ],
defaultValue: functionCode == 'temp_set' defaultValue: 0);
? 200
: functionCode == 'temp_current'
? -100
: 0,
);
}, },
), ),
), ),
@ -206,27 +203,61 @@ class ACHelper {
required String operationName, required String operationName,
bool? removeComparators, bool? removeComparators,
}) { }) {
final initialVal = selectedFunction == 'temp_set' ? 200 : -100; final selectedFn =
acFunctions.firstWhere((f) => f.code == selectedFunction);
if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') {
final initialValue = selectedFunctionData?.value ?? initialVal; // Convert stored integer value to display value
return _buildTemperatureSelector( final displayValue =
context: context, (selectedFunctionData?.value ?? selectedFn.min ?? 0) / 10;
initialValue: initialValue, final minValue = selectedFn.min! / 10;
selectCode: selectedFunction, final maxValue = selectedFn.max! / 10;
return CustomRoutinesTextbox(
withSpecialChar: true,
dividendOfRange: maxValue,
currentCondition: selectedFunctionData?.condition, currentCondition: selectedFunctionData?.condition,
device: device, dialogType: selectedFn.type,
operationName: operationName, sliderRange: (minValue, maxValue),
selectedFunctionData: selectedFunctionData, displayedValue: displayValue.toStringAsFixed(1),
removeComparators: removeComparators, initialValue: displayValue.toDouble(),
unit: selectedFn.unit!,
onConditionChanged: (condition) => context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: selectedFn.operationName,
condition: condition,
value: 0,
step: selectedFn.step,
unit: selectedFn.unit,
max: selectedFn.max,
min: selectedFn.min,
),
),
),
onTextChanged: (value) => context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectedFunction,
operationName: selectedFn.operationName,
value: (value * 10).round(), // Store as integer
condition: selectedFunctionData?.condition,
step: selectedFn.step,
unit: selectedFn.unit,
max: selectedFn.max,
min: selectedFn.min,
),
),
),
stepIncreaseAmount: selectedFn.step! / 10, // Convert step for display
); );
} }
final selectedFn = acFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
context: context, context: context,
values: values, values: selectedFn.getOperationalValues(),
selectedValue: selectedFunctionData?.value, selectedValue: selectedFunctionData?.value,
device: device, device: device,
operationName: operationName, operationName: operationName,
@ -235,150 +266,151 @@ class ACHelper {
); );
} }
/// Build temperature selector for AC functions dialog // /// Build temperature selector for AC functions dialog
static Widget _buildTemperatureSelector({ // static Widget _buildTemperatureSelector({
required BuildContext context, // required BuildContext context,
required dynamic initialValue, // required dynamic initialValue,
required String? currentCondition, // required String? currentCondition,
required String selectCode, // required String selectCode,
AllDevicesModel? device, // AllDevicesModel? device,
required String operationName, // required String operationName,
DeviceFunctionData? selectedFunctionData, // DeviceFunctionData? selectedFunctionData,
bool? removeComparators, // bool? removeComparators,
}) { // }) {
return Column( // return Column(
mainAxisAlignment: MainAxisAlignment.center, // mainAxisAlignment: MainAxisAlignment.center,
children: [ // children: [
if (removeComparators != true) // if (removeComparators != true)
_buildConditionToggle( // _buildConditionToggle(
context, // context,
currentCondition, // currentCondition,
selectCode, // selectCode,
device, // device,
operationName, // operationName,
selectedFunctionData, // selectedFunctionData,
), // ),
const SizedBox(height: 20), // const SizedBox(height: 20),
_buildTemperatureDisplay( // _buildTemperatureDisplay(
context, // context,
initialValue, // initialValue,
device, // device,
operationName, // operationName,
selectedFunctionData, // selectedFunctionData,
selectCode, // selectCode,
), // ),
const SizedBox(height: 20), // const SizedBox(height: 20),
_buildTemperatureSlider( // _buildTemperatureSlider(
context, // context,
initialValue, // initialValue,
device, // device,
operationName, // operationName,
selectedFunctionData, // selectedFunctionData,
selectCode, // selectCode,
), // ),
], // ],
); // );
} // }
/// Build condition toggle for AC functions dialog // /// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle( // static Widget _buildConditionToggle(
BuildContext context, // BuildContext context,
String? currentCondition, // String? currentCondition,
String selectCode, // String selectCode,
AllDevicesModel? device, // AllDevicesModel? device,
String operationName, // String operationName,
DeviceFunctionData? selectedFunctionData, // DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged, // // Function(String) onConditionChanged,
) { // ) {
final conditions = ["<", "==", ">"]; // final conditions = ["<", "==", ">"];
return ToggleButtons( // return ToggleButtons(
onPressed: (int index) { // onPressed: (int index) {
context.read<FunctionBloc>().add( // context.read<FunctionBloc>().add(
AddFunction( // AddFunction(
functionData: DeviceFunctionData( // functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', // entityId: device?.uuid ?? '',
functionCode: selectCode, // functionCode: selectCode,
operationName: operationName, // operationName: operationName,
condition: conditions[index], // condition: conditions[index],
value: selectedFunctionData?.value ?? selectCode == 'temp_set' // value: selectedFunctionData?.value ?? selectCode == 'temp_set'
? 200 // ? 200
: -100, // : -100,
valueDescription: selectedFunctionData?.valueDescription, // valueDescription: selectedFunctionData?.valueDescription,
), // ),
), // ),
); // );
}, // },
borderRadius: const BorderRadius.all(Radius.circular(8)), // borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: ColorsManager.primaryColorWithOpacity, // selectedBorderColor: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white, // selectedColor: Colors.white,
fillColor: ColorsManager.primaryColorWithOpacity, // fillColor: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity, // color: ColorsManager.primaryColorWithOpacity,
constraints: const BoxConstraints( // constraints: const BoxConstraints(
minHeight: 40.0, // minHeight: 40.0,
minWidth: 40.0, // minWidth: 40.0,
), // ),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), // isSelected:
children: conditions.map((c) => Text(c)).toList(), // conditions.map((c) => c == (currentCondition ?? "==")).toList(),
); // children: conditions.map((c) => Text(c)).toList(),
} // );
// }
/// Build temperature display for AC functions dialog // /// Build temperature display for AC functions dialog
static Widget _buildTemperatureDisplay( // static Widget _buildTemperatureDisplay(
BuildContext context, // BuildContext context,
dynamic initialValue, // dynamic initialValue,
AllDevicesModel? device, // AllDevicesModel? device,
String operationName, // String operationName,
DeviceFunctionData? selectedFunctionData, // DeviceFunctionData? selectedFunctionData,
String selectCode, // String selectCode,
) { // ) {
final initialVal = selectCode == 'temp_set' ? 200 : -100; // final initialVal = selectCode == 'temp_set' ? 200 : -100;
return Container( // return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), // padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration( // decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1), // color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10), // borderRadius: BorderRadius.circular(10),
), // ),
child: Text( // child: Text(
'${(initialValue ?? initialVal) / 10}°C', // '${(initialValue ?? initialVal) / 10}°C',
style: context.textTheme.headlineMedium!.copyWith( // style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity, // color: ColorsManager.primaryColorWithOpacity,
), // ),
), // ),
); // );
} // }
static Widget _buildTemperatureSlider( // static Widget _buildTemperatureSlider(
BuildContext context, // BuildContext context,
dynamic initialValue, // dynamic initialValue,
AllDevicesModel? device, // AllDevicesModel? device,
String operationName, // String operationName,
DeviceFunctionData? selectedFunctionData, // DeviceFunctionData? selectedFunctionData,
String selectCode, // String selectCode,
) { // ) {
return Slider( // return Slider(
value: initialValue is int ? initialValue.toDouble() : 200.0, // value: initialValue is int ? initialValue.toDouble() : 200.0,
min: selectCode == 'temp_current' ? -100 : 200, // min: selectCode == 'temp_current' ? -100 : 200,
max: selectCode == 'temp_current' ? 900 : 300, // max: selectCode == 'temp_current' ? 900 : 300,
divisions: 10, // divisions: 10,
label: '${((initialValue ?? 160) / 10).toInt()}°C', // label: '${((initialValue ?? 160) / 10).toInt()}°C',
onChanged: (value) { // onChanged: (value) {
context.read<FunctionBloc>().add( // context.read<FunctionBloc>().add(
AddFunction( // AddFunction(
functionData: DeviceFunctionData( // functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', // entityId: device?.uuid ?? '',
functionCode: selectCode, // functionCode: selectCode,
operationName: operationName, // operationName: operationName,
value: value, // value: value,
condition: selectedFunctionData?.condition, // condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription, // valueDescription: selectedFunctionData?.valueDescription,
), // ),
), // ),
); // );
}, // },
); // );
} // }
static Widget _buildOperationalValuesList({ static Widget _buildOperationalValuesList({
required BuildContext context, required BuildContext context,
@ -414,7 +446,9 @@ class ACHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@ -430,7 +464,8 @@ class ACHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription, valueDescription:
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -41,7 +41,8 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
void initState() { void initState() {
super.initState(); super.initState();
_cpsFunctions = widget.functions.whereType<CpsFunctions>().where((function) { _cpsFunctions =
widget.functions.whereType<CpsFunctions>().where((function) {
if (widget.dialogType == 'THEN') { if (widget.dialogType == 'THEN') {
return function.type == 'THEN' || function.type == 'BOTH'; return function.type == 'THEN' || function.type == 'BOTH';
} }
@ -149,6 +150,7 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
device: widget.device, device: widget.device,
) )
: CpsDialogSliderSelector( : CpsDialogSliderSelector(
step: selectedCpsFunctions.step!,
operations: operations, operations: operations,
selectedFunction: selectedFunction ?? '', selectedFunction: selectedFunction ?? '',
selectedFunctionData: selectedFunctionData, selectedFunctionData: selectedFunctionData,

View File

@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart'; import 'package:syncrow_web/pages/routines/models/ceiling_presence_sensor_functions.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/ceiling_sensor/cps_slider_helpers.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
@ -16,6 +17,7 @@ class CpsDialogSliderSelector extends StatelessWidget {
required this.device, required this.device,
required this.operationName, required this.operationName,
required this.dialogType, required this.dialogType,
required this.step,
super.key, super.key,
}); });
@ -26,13 +28,16 @@ class CpsDialogSliderSelector extends StatelessWidget {
final AllDevicesModel? device; final AllDevicesModel? device;
final String operationName; final String operationName;
final String dialogType; final String dialogType;
final double step;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SliderValueSelector( return CustomRoutinesTextbox(
withSpecialChar: false,
currentCondition: selectedFunctionData.condition, currentCondition: selectedFunctionData.condition,
dialogType: dialogType, dialogType: dialogType,
sliderRange: CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode), sliderRange:
CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode),
displayedValue: CpsSliderHelpers.displayText( displayedValue: CpsSliderHelpers.displayText(
value: selectedFunctionData.value, value: selectedFunctionData.value,
functionCode: selectedFunctionData.functionCode, functionCode: selectedFunctionData.functionCode,
@ -50,7 +55,7 @@ class CpsDialogSliderSelector extends StatelessWidget {
), ),
), ),
), ),
onSliderChanged: (value) => context.read<FunctionBloc>().add( onTextChanged: (value) => context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
@ -64,6 +69,7 @@ class CpsDialogSliderSelector extends StatelessWidget {
dividendOfRange: CpsSliderHelpers.dividendOfRange( dividendOfRange: CpsSliderHelpers.dividendOfRange(
selectedFunctionData.functionCode, selectedFunctionData.functionCode,
), ),
stepIncreaseAmount: step,
); );
} }
} }

View File

@ -36,11 +36,14 @@ class CpsFunctionsList extends StatelessWidget {
return RoutineDialogFunctionListTile( return RoutineDialogFunctionListTile(
iconPath: function.icon, iconPath: function.icon,
operationName: function.operationName, operationName: function.operationName,
onTap: () => RoutineTapFunctionHelper.onTapFunction( onTap: () {
RoutineTapFunctionHelper.onTapFunction(
context, context,
step: function.step,
functionCode: function.code, functionCode: function.code,
functionOperationName: function.operationName, functionOperationName: function.operationName,
functionValueDescription: selectedFunctionData?.valueDescription, functionValueDescription:
selectedFunctionData?.valueDescription,
deviceUuid: device?.uuid, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [ codesToAddIntoFunctionsWithDefaultValue: [
'static_max_dis', 'static_max_dis',
@ -56,8 +59,8 @@ class CpsFunctionsList extends StatelessWidget {
'presence_range', 'presence_range',
if (dialogType == "IF") 'sensitivity', if (dialogType == "IF") 'sensitivity',
], ],
),
); );
});
}, },
), ),
); );

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/flush/flush_operational_value.dart';
@ -24,19 +25,17 @@ class FlushOperationalValuesList extends StatelessWidget {
return ListView.builder( return ListView.builder(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
itemCount: values.length, itemCount: values.length,
itemBuilder: (context, index) => itemBuilder: (context, index) => _buildValueItem(context, values[index]),
_buildValueItem(context, values[index]),
); );
} }
Widget _buildValueItem(BuildContext context, FlushOperationalValue value) { Widget _buildValueItem(BuildContext context, FlushOperationalValue value) {
return Padding( return Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
SvgPicture.asset(value.icon, width: 25, height: 25),
Expanded(child: _buildValueDescription(value)), Expanded(child: _buildValueDescription(value)),
_buildValueRadio(context, value), _buildValueRadio(context, value),
], ],
@ -44,9 +43,6 @@ class FlushOperationalValuesList extends StatelessWidget {
); );
} }
Widget _buildValueDescription(FlushOperationalValue value) { Widget _buildValueDescription(FlushOperationalValue value) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
@ -60,6 +56,4 @@ class FlushOperationalValuesList extends StatelessWidget {
groupValue: selectedValue, groupValue: selectedValue,
onChanged: (_) => onSelect(value)); onChanged: (_) => onSelect(value));
} }
} }

View File

@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart'; import 'package:syncrow_web/pages/routines/models/flush/flush_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/flush_presence_sensor/flush_operational_values_list.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart'; import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
@ -66,7 +67,8 @@ class FlushValueSelectorWidget extends StatelessWidget {
if (isDistanceDetection) { if (isDistanceDetection) {
initialValue = initialValue / 100; initialValue = initialValue / 100;
} }
return SliderValueSelector( return CustomRoutinesTextbox(
withSpecialChar: true,
currentCondition: functionData.condition, currentCondition: functionData.condition,
dialogType: dialogType, dialogType: dialogType,
sliderRange: sliderRange, sliderRange: sliderRange,
@ -83,7 +85,7 @@ class FlushValueSelectorWidget extends StatelessWidget {
), ),
), ),
), ),
onSliderChanged: (value) { onTextChanged: (value) {
final roundedValue = _roundToStep(value, stepSize); final roundedValue = _roundToStep(value, stepSize);
final finalValue = final finalValue =
isDistanceDetection ? (roundedValue * 100).toInt() : roundedValue; isDistanceDetection ? (roundedValue * 100).toInt() : roundedValue;
@ -102,6 +104,7 @@ class FlushValueSelectorWidget extends StatelessWidget {
}, },
unit: _unit, unit: _unit,
dividendOfRange: stepSize, dividendOfRange: stepSize,
stepIncreaseAmount: stepSize,
); );
} }

View File

@ -8,6 +8,7 @@ abstract final class RoutineTapFunctionHelper {
static void onTapFunction( static void onTapFunction(
BuildContext context, { BuildContext context, {
double? step,
required String functionCode, required String functionCode,
required String functionOperationName, required String functionOperationName,
required String? functionValueDescription, required String? functionValueDescription,

View File

@ -4,11 +4,11 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/one_gang_switch/one_gang_switch.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
@ -87,14 +87,15 @@ class OneGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () => onTap: () => RoutineTapFunctionHelper
RoutineTapFunctionHelper.onTapFunction( .onTapFunction(
context, context,
functionCode: function.code, functionCode: function.code,
functionOperationName: functionOperationName:
function.operationName, function.operationName,
functionValueDescription: functionValueDescription:
selectedFunctionData.valueDescription, selectedFunctionData
.valueDescription,
deviceUuid: device?.uuid, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [ codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1', 'countdown_1',
@ -110,12 +111,14 @@ class OneGangSwitchHelper {
child: _buildValueSelector( child: _buildValueSelector(
context: context, context: context,
selectedFunction: selectedFunction, selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData, selectedFunctionData:
selectedFunctionData,
acFunctions: oneGangFunctions, acFunctions: oneGangFunctions,
device: device, device: device,
operationName: selectedOperationName ?? '', operationName:
selectedOperationName ?? '',
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
), dialogType: dialogType),
), ),
], ],
), ),
@ -172,6 +175,7 @@ class OneGangSwitchHelper {
AllDevicesModel? device, AllDevicesModel? device,
required String operationName, required String operationName,
required bool removeComparetors, required bool removeComparetors,
required String dialogType,
}) { }) {
if (selectedFunction == 'countdown_1') { if (selectedFunction == 'countdown_1') {
final initialValue = selectedFunctionData?.value ?? 0; final initialValue = selectedFunctionData?.value ?? 0;
@ -184,6 +188,7 @@ class OneGangSwitchHelper {
operationName: operationName, operationName: operationName,
selectedFunctionData: selectedFunctionData, selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
dialogType: dialogType,
); );
} }
final selectedFn = acFunctions.firstWhere( final selectedFn = acFunctions.firstWhere(
@ -216,93 +221,18 @@ class OneGangSwitchHelper {
required String operationName, required String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
required bool removeComparetors, required bool removeComparetors,
String? dialogType,
}) { }) {
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (removeComparetors != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName, _buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode), selectedFunctionData, selectCode, dialogType!),
], ],
); );
} }
/// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle(
BuildContext context,
String? currentCondition,
String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged,
) {
final conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (int index) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white,
fillColor: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity,
constraints: const BoxConstraints(
minHeight: 40.0,
minWidth: 40.0,
),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
);
}
/// Build temperature display for AC functions dialog
static Widget _buildCountDownDisplay(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0),
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildCountDownSlider( static Widget _buildCountDownSlider(
BuildContext context, BuildContext context,
dynamic initialValue, dynamic initialValue,
@ -310,38 +240,47 @@ class OneGangSwitchHelper {
String operationName, String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
String selectCode, String selectCode,
String dialogType,
) { ) {
const twelveHoursInSeconds = 43200.0; return CustomRoutinesTextbox(
final operationalValues = SwitchOperationalValue( withSpecialChar: false,
icon: '', currentCondition: selectedFunctionData?.condition,
description: "sec", dialogType: dialogType,
value: 0.0, sliderRange: (0, 43200),
minValue: 0, displayedValue: (initialValue ?? 0).toString(),
maxValue: twelveHoursInSeconds, initialValue: (initialValue ?? 0).toString(),
stepValue: 1, onConditionChanged: (condition) {
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions:
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
functionCode: selectCode, functionCode: selectCode,
operationName: operationName, operationName: operationName,
value: value, condition: condition,
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
onTextChanged: (value) {
final roundedValue = value.round();
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: roundedValue,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription, valueDescription: selectedFunctionData?.valueDescription,
), ),
), ),
); );
}, },
unit: 'sec',
dividendOfRange: 1,
stepIncreaseAmount: 1,
); );
} }
@ -377,7 +316,9 @@ class OneGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@ -393,7 +334,8 @@ class OneGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription, valueDescription:
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -4,10 +4,10 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
@ -86,20 +86,21 @@ class ThreeGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () => onTap: () => RoutineTapFunctionHelper
RoutineTapFunctionHelper.onTapFunction( .onTapFunction(
context, context,
functionCode: function.code, functionCode: function.code,
functionOperationName: functionOperationName:
function.operationName, function.operationName,
functionValueDescription: functionValueDescription:
selectedFunctionData.valueDescription, selectedFunctionData
.valueDescription,
deviceUuid: device?.uuid, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [ codesToAddIntoFunctionsWithDefaultValue:
'countdown_1', function.code
'countdown_2', .startsWith('countdown')
'countdown_3', ? [function.code]
], : [],
), ),
); );
}, },
@ -111,12 +112,14 @@ class ThreeGangSwitchHelper {
child: _buildValueSelector( child: _buildValueSelector(
context: context, context: context,
selectedFunction: selectedFunction, selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData, selectedFunctionData:
selectedFunctionData,
switchFunctions: switchFunctions, switchFunctions: switchFunctions,
device: device, device: device,
operationName: selectedOperationName ?? '', operationName:
selectedOperationName ?? '',
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
), dialogType: dialogType),
), ),
], ],
), ),
@ -133,14 +136,6 @@ class ThreeGangSwitchHelper {
onConfirm: state.addedFunctions.isNotEmpty onConfirm: state.addedFunctions.isNotEmpty
? () { ? () {
/// add the functions to the routine bloc /// add the functions to the routine bloc
// for (var function in state.addedFunctions) {
// context.read<RoutineBloc>().add(
// AddFunctionToRoutine(
// function,
// uniqueCustomId,
// ),
// );
// }
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
AddFunctionToRoutine( AddFunctionToRoutine(
state.addedFunctions, state.addedFunctions,
@ -173,6 +168,7 @@ class ThreeGangSwitchHelper {
AllDevicesModel? device, AllDevicesModel? device,
required String operationName, required String operationName,
required bool removeComparetors, required bool removeComparetors,
required String dialogType,
}) { }) {
if (selectedFunction == 'countdown_1' || if (selectedFunction == 'countdown_1' ||
selectedFunction == 'countdown_2' || selectedFunction == 'countdown_2' ||
@ -187,10 +183,11 @@ class ThreeGangSwitchHelper {
operationName: operationName, operationName: operationName,
selectedFunctionData: selectedFunctionData, selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
); dialogType: dialogType);
} }
final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction); final selectedFn =
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@ -213,93 +210,18 @@ class ThreeGangSwitchHelper {
required String operationName, required String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
bool? removeComparetors, bool? removeComparetors,
required String dialogType,
}) { }) {
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (removeComparetors != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName, _buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode), selectedFunctionData, selectCode, dialogType),
], ],
); );
} }
/// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle(
BuildContext context,
String? currentCondition,
String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged,
) {
final conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (int index) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white,
fillColor: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity,
constraints: const BoxConstraints(
minHeight: 40.0,
minWidth: 40.0,
),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
);
}
/// Build temperature display for AC functions dialog
static Widget _buildCountDownDisplay(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0),
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildCountDownSlider( static Widget _buildCountDownSlider(
BuildContext context, BuildContext context,
dynamic initialValue, dynamic initialValue,
@ -307,38 +229,47 @@ class ThreeGangSwitchHelper {
String operationName, String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
String selectCode, String selectCode,
String dialogType,
) { ) {
const twelveHoursInSeconds = 43200.0; return CustomRoutinesTextbox(
final operationalValues = SwitchOperationalValue( withSpecialChar: true,
icon: '', currentCondition: selectedFunctionData?.condition,
description: "sec", dialogType: dialogType,
value: 0.0, sliderRange: (0, 43200),
minValue: 0, displayedValue: (initialValue ?? 0).toString(),
maxValue: twelveHoursInSeconds, initialValue: (initialValue ?? 0).toString(),
stepValue: 1, onConditionChanged: (condition) {
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions:
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
functionCode: selectCode, functionCode: selectCode,
operationName: operationName, operationName: operationName,
value: value, condition: condition,
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
onTextChanged: (value) {
final roundedValue = value.round();
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: roundedValue,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription, valueDescription: selectedFunctionData?.valueDescription,
), ),
), ),
); );
}, },
unit: 'sec',
dividendOfRange: 1,
stepIncreaseAmount: 1,
); );
} }
@ -374,7 +305,9 @@ class ThreeGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@ -390,7 +323,8 @@ class ThreeGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription, valueDescription:
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -8,6 +8,7 @@ import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart'; import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/helpers/routine_tap_function_helper.dart';
@ -86,14 +87,15 @@ class TwoGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () => onTap: () => RoutineTapFunctionHelper
RoutineTapFunctionHelper.onTapFunction( .onTapFunction(
context, context,
functionCode: function.code, functionCode: function.code,
functionOperationName: functionOperationName:
function.operationName, function.operationName,
functionValueDescription: functionValueDescription:
selectedFunctionData.valueDescription, selectedFunctionData
.valueDescription,
deviceUuid: device?.uuid, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [ codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1', 'countdown_1',
@ -115,6 +117,7 @@ class TwoGangSwitchHelper {
device: device, device: device,
operationName: selectedOperationName ?? '', operationName: selectedOperationName ?? '',
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
dialogType: dialogType,
), ),
), ),
], ],
@ -172,8 +175,10 @@ class TwoGangSwitchHelper {
AllDevicesModel? device, AllDevicesModel? device,
required String operationName, required String operationName,
required bool removeComparetors, required bool removeComparetors,
required String dialogType,
}) { }) {
if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') { if (selectedFunction == 'countdown_1' ||
selectedFunction == 'countdown_2') {
final initialValue = selectedFunctionData?.value ?? 0; final initialValue = selectedFunctionData?.value ?? 0;
return _buildTemperatureSelector( return _buildTemperatureSelector(
context: context, context: context,
@ -184,10 +189,11 @@ class TwoGangSwitchHelper {
operationName: operationName, operationName: operationName,
selectedFunctionData: selectedFunctionData, selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
); dialogType: dialogType);
} }
final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction); final selectedFn =
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@ -210,25 +216,13 @@ class TwoGangSwitchHelper {
required String operationName, required String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
bool? removeComparetors, bool? removeComparetors,
String? dialogType,
}) { }) {
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (removeComparetors != true)
_buildConditionToggle(
context,
currentCondition,
selectCode,
device,
operationName,
selectedFunctionData,
),
const SizedBox(height: 20),
_buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode),
const SizedBox(height: 20),
_buildCountDownSlider(context, initialValue, device, operationName, _buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode), selectedFunctionData, selectCode, dialogType!),
], ],
); );
} }
@ -269,7 +263,8 @@ class TwoGangSwitchHelper {
minHeight: 40.0, minHeight: 40.0,
minWidth: 40.0, minWidth: 40.0,
), ),
isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(), isSelected:
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(), children: conditions.map((c) => Text(c)).toList(),
); );
} }
@ -304,38 +299,48 @@ class TwoGangSwitchHelper {
String operationName, String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
String selectCode, String selectCode,
String dialogType,
) { ) {
const twelveHoursInSeconds = 43200.0; return CustomRoutinesTextbox(
final operationalValues = SwitchOperationalValue( withSpecialChar: true,
icon: '', currentCondition: selectedFunctionData?.condition,
description: "sec", dialogType: dialogType,
value: 0.0, sliderRange: (0, 43200),
minValue: 0, displayedValue: (initialValue ?? 0).toString(),
maxValue: twelveHoursInSeconds, initialValue: (initialValue ?? 0).toString(),
stepValue: 1, onConditionChanged: (condition) {
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions:
(((operationalValues.maxValue ?? 0) - (operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
functionCode: selectCode, functionCode: selectCode,
operationName: operationName, operationName: operationName,
value: value, condition: condition,
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
onTextChanged: (value) {
final roundedValue =
value.round(); // Round to nearest integer (stepSize 1)
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: roundedValue,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription, valueDescription: selectedFunctionData?.valueDescription,
), ),
), ),
); );
}, },
unit: 'sec',
dividendOfRange: 1,
stepIncreaseAmount: 1,
); );
} }
@ -371,7 +376,9 @@ class TwoGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked, isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@ -387,7 +394,8 @@ class TwoGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription, valueDescription:
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -4,8 +4,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart'; import 'package:syncrow_web/pages/routines/models/wps/wps_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_operational_values_list.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/wall_sensor/wps_operational_values_list.dart';
import 'package:syncrow_web/pages/routines/widgets/slider_value_selector.dart';
class WpsValueSelectorWidget extends StatelessWidget { class WpsValueSelectorWidget extends StatelessWidget {
final String selectedFunction; final String selectedFunction;
@ -27,11 +27,13 @@ class WpsValueSelectorWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final selectedFn = wpsFunctions.firstWhere((f) => f.code == selectedFunction); final selectedFn =
wpsFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
if (_isSliderFunction(selectedFunction)) { if (_isSliderFunction(selectedFunction)) {
return SliderValueSelector( return CustomRoutinesTextbox(
withSpecialChar: false,
currentCondition: functionData.condition, currentCondition: functionData.condition,
dialogType: dialogType, dialogType: dialogType,
sliderRange: sliderRange, sliderRange: sliderRange,
@ -48,7 +50,7 @@ class WpsValueSelectorWidget extends StatelessWidget {
), ),
), ),
), ),
onSliderChanged: (value) => context.read<FunctionBloc>().add( onTextChanged: (value) => context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
@ -61,6 +63,7 @@ class WpsValueSelectorWidget extends StatelessWidget {
), ),
unit: _unit, unit: _unit,
dividendOfRange: 1, dividendOfRange: 1,
stepIncreaseAmount: _steps,
); );
} }
@ -99,4 +102,10 @@ class WpsValueSelectorWidget extends StatelessWidget {
'illuminance_value' => 'Lux', 'illuminance_value' => 'Lux',
_ => '', _ => '',
}; };
double get _steps => switch (functionData.functionCode) {
'presence_time' => 1,
'dis_current' => 1,
'illuminance_value' => 1,
_ => 1,
};
} }

View File

@ -176,6 +176,7 @@ class _WaterHeaterDialogRoutinesState extends State<WaterHeaterDialogRoutines> {
functionData: functionData, functionData: functionData,
whFunctions: _waterHeaterFunctions, whFunctions: _waterHeaterFunctions,
device: widget.device, device: widget.device,
dialogType: widget.dialogType,
), ),
); );
} }

View File

@ -2,25 +2,24 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routines/helper/duration_format_helper.dart';
import 'package:syncrow_web/pages/routines/models/device_functions.dart'; import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_functions.dart'; import 'package:syncrow_web/pages/routines/models/water_heater/water_heater_functions.dart';
import 'package:syncrow_web/pages/routines/widgets/custom_routines_textbox.dart';
import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/water_heater/water_heater_operational_values_list.dart'; import 'package:syncrow_web/pages/routines/widgets/routine_dialogs/water_heater/water_heater_operational_values_list.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class WaterHeaterValueSelectorWidget extends StatelessWidget { class WaterHeaterValueSelectorWidget extends StatelessWidget {
final String selectedFunction; final String selectedFunction;
final DeviceFunctionData functionData; final DeviceFunctionData functionData;
final List<WaterHeaterFunctions> whFunctions; final List<WaterHeaterFunctions> whFunctions;
final AllDevicesModel? device; final AllDevicesModel? device;
final String dialogType;
const WaterHeaterValueSelectorWidget({ const WaterHeaterValueSelectorWidget({
required this.selectedFunction, required this.selectedFunction,
required this.functionData, required this.functionData,
required this.whFunctions, required this.whFunctions,
required this.device, required this.device,
required this.dialogType,
super.key, super.key,
}); });
@ -39,22 +38,6 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
_buildConditionToggle(
context,
functionData.condition,
selectedFunction,
device,
selectedFn.operationName,
functionData,
),
_buildCountDownDisplay(
context,
functionData.value,
device,
selectedFn.operationName,
functionData,
selectedFunction,
),
_buildCountDownSlider( _buildCountDownSlider(
context, context,
functionData.value, functionData.value,
@ -62,6 +45,7 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget {
selectedFn.operationName, selectedFn.operationName,
functionData, functionData,
selectedFunction, selectedFunction,
dialogType
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
], ],
@ -90,28 +74,6 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget {
); );
} }
static Widget _buildCountDownDisplay(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: Text(
DurationFormatMixin.formatDuration(initialValue?.toInt() ?? 0),
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildCountDownSlider( static Widget _buildCountDownSlider(
BuildContext context, BuildContext context,
dynamic initialValue, dynamic initialValue,
@ -119,78 +81,47 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget {
String operationName, String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
String selectCode, String selectCode,
String dialogType,
) { ) {
const twelveHoursInSeconds = 43200.0; return CustomRoutinesTextbox(
final operationalValues = SwitchOperationalValue( withSpecialChar: false,
icon: '', currentCondition: selectedFunctionData?.condition,
description: "sec", dialogType: dialogType,
value: 0.0, sliderRange: (0, 43200),
minValue: 0, displayedValue: (initialValue ?? 0).toString(),
maxValue: twelveHoursInSeconds, initialValue: (initialValue ?? 0).toString(),
stepValue: 1, onConditionChanged: (condition) {
);
return Slider(
value: (initialValue ?? 0).toDouble(),
min: operationalValues.minValue?.toDouble() ?? 0.0,
max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: (((operationalValues.maxValue ?? 0) -
(operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) {
context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
functionCode: selectCode, functionCode: selectCode,
operationName: operationName, operationName: operationName,
value: value, value: condition,
condition: condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
onTextChanged: (value) {
final roundedValue = value.round();
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: roundedValue,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription, valueDescription: selectedFunctionData?.valueDescription,
), ),
), ),
); );
}, },
); unit: 'sec',
} dividendOfRange: 1,
stepIncreaseAmount: 1,
static Widget _buildConditionToggle(
BuildContext context,
String? currentCondition,
String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
) {
final conditions = ["<", "==", ">"];
return ToggleButtons(
onPressed: (int index) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
condition: conditions[index],
value: selectedFunctionData?.value ?? 0,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white,
fillColor: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity,
constraints: const BoxConstraints(
minHeight: 40.0,
minWidth: 40.0,
),
isSelected:
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(),
); );
} }
} }

View File

@ -41,7 +41,16 @@ class _UserDropdownMenuState extends State<UserDropdownMenu> {
_isDropdownOpen = false; _isDropdownOpen = false;
}); });
}, },
child: Transform.rotate( child: Row(
children: [
const SizedBox(width: 12),
if (widget.user != null)
Text(
'${widget.user!.firstName} ${widget.user!.lastName}',
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(width: 12),
Transform.rotate(
angle: _isDropdownOpen ? -1.5708 : 1.5708, angle: _isDropdownOpen ? -1.5708 : 1.5708,
child: const Icon( child: const Icon(
Icons.arrow_forward_ios, Icons.arrow_forward_ios,
@ -49,6 +58,8 @@ class _UserDropdownMenuState extends State<UserDropdownMenu> {
size: 16, size: 16,
), ),
), ),
],
),
), ),
], ],
); );

View File

@ -92,13 +92,6 @@ class DesktopAppBar extends StatelessWidget {
if (rightBody != null) rightBody!, if (rightBody != null) rightBody!,
const SizedBox(width: 24), const SizedBox(width: 24),
_UserAvatar(), _UserAvatar(),
const SizedBox(width: 12),
if (user != null)
Text(
'${user.firstName} ${user.lastName}',
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(width: 12),
UserDropdownMenu(user: user), UserDropdownMenu(user: user),
], ],
); );
@ -146,14 +139,6 @@ class TabletAppBar extends StatelessWidget {
if (rightBody != null) rightBody!, if (rightBody != null) rightBody!,
const SizedBox(width: 16), const SizedBox(width: 16),
_UserAvatar(), _UserAvatar(),
if (user != null) ...[
const SizedBox(width: 8),
Text(
'${user.firstName} ${user.lastName}',
style:
Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: 14),
),
],
UserDropdownMenu(user: user), UserDropdownMenu(user: user),
], ],
); );
@ -215,14 +200,6 @@ class MobileAppBar extends StatelessWidget {
return Row( return Row(
children: [ children: [
_UserAvatar(), _UserAvatar(),
if (user != null) ...[
const SizedBox(width: 8),
Text(
'${user.firstName} ${user.lastName}',
style:
Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: 14),
),
],
UserDropdownMenu(user: user), UserDropdownMenu(user: user),
], ],
); );