Compare commits

..

8 Commits

34 changed files with 995 additions and 1013 deletions

View File

@ -10,7 +10,6 @@
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,22 +1,28 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class OccupancyHeatMapModel extends Equatable { class OccupancyHeatMapModel extends Equatable {
final DateTime date; final String uuid;
final int occupancy; final DateTime eventDate;
final int countTotalPresenceDetected;
const OccupancyHeatMapModel({ const OccupancyHeatMapModel({
required this.date, required this.uuid,
required this.occupancy, required this.eventDate,
required this.countTotalPresenceDetected,
}); });
factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) { factory OccupancyHeatMapModel.fromJson(Map<String, dynamic> json) {
return OccupancyHeatMapModel( return OccupancyHeatMapModel(
date: DateTime.parse(json['date'] as String), uuid: json['uuid'] as String? ?? '',
occupancy: json['occupancy'] as int, eventDate: DateTime.parse(
json['event_date'] as String? ?? '${DateTime.now()}',
),
countTotalPresenceDetected: json['count_total_presence_detected'] as int? ?? 0,
); );
} }
@override @override
List<Object?> get props => [date, occupancy]; List<Object?> get props => [uuid, eventDate, countTotalPresenceDetected];
} }

View File

@ -14,7 +14,7 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_he
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/fake_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/fake_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/fake_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';
import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart'; import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart'; import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart';
@ -60,7 +60,9 @@ class AnalyticsPage extends StatelessWidget {
), ),
BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())), BlocProvider(create: (context) => OccupancyBloc(FakeOccupacyService())),
BlocProvider( BlocProvider(
create: (context) => OccupancyHeatMapBloc(FakeOccupancyHeatMapService()), create: (context) => OccupancyHeatMapBloc(
RemoteOccupancyHeatMapService(HTTPService()),
),
), ),
BlocProvider(create: (context) => AnalyticsDatePickerBloc()), BlocProvider(create: (context) => AnalyticsDatePickerBloc()),
], ],

View File

@ -35,8 +35,7 @@ abstract final class FetchOccupancyDataHelper {
context.read<OccupancyHeatMapBloc>().add( context.read<OccupancyHeatMapBloc>().add(
LoadOccupancyHeatMapEvent( LoadOccupancyHeatMapEvent(
GetOccupancyHeatMapParam( GetOccupancyHeatMapParam(
spaceId: spaceId, spaceUuid: spaceId,
communityId: communityId,
year: datePickerState.yearlyDate, year: datePickerState.yearlyDate,
), ),
), ),

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

@ -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';
@ -15,7 +16,7 @@ class OccupancyHeatMap extends StatelessWidget {
static const _totalWeeks = 53; static const _totalWeeks = 53;
int get _maxValue => heatMapData.isNotEmpty int get _maxValue => heatMapData.isNotEmpty
? heatMapData.keys.map((key) => heatMapData[key]!).reduce(math.max) ? heatMapData.keys.map((key) => heatMapData[key] ?? 0).reduce(math.max)
: 0; : 0;
DateTime _getStartingDate() { DateTime _getStartingDate() {
@ -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), items: paintItems,
painter: OccupancyPainter( maxValue: _maxValue,
items: paintItems, cellSize: _cellSize,
maxValue: _maxValue,
),
), ),
), ),
], ],

View File

@ -68,7 +68,10 @@ class OccupancyHeatMapBox extends StatelessWidget {
Expanded( Expanded(
child: OccupancyHeatMap( child: OccupancyHeatMap(
heatMapData: state.heatMapData.asMap().map( heatMapData: state.heatMapData.asMap().map(
(_, value) => MapEntry(value.date, value.occupancy), (_, value) => MapEntry(
value.eventDate,
value.countTotalPresenceDetected,
),
), ),
), ),
), ),

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,22 +48,27 @@ 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);
_drawDashedLine( // Highlight the hovered item
canvas, if (hoveredItem != null && hoveredItem!.index == item.index) {
Offset(x, y), canvas.drawRect(rect, hoveredBorderPaint);
Offset(x + cellSize, y), } else {
borderPaint, _drawDashedLine(
); canvas,
_drawDashedLine( Offset(x, y),
canvas, Offset(x + cellSize, y),
Offset(x, y + cellSize), borderPaint,
Offset(x + cellSize, y + cellSize), );
borderPaint, _drawDashedLine(
); canvas,
Offset(x, y + cellSize),
Offset(x + cellSize, y + cellSize),
borderPaint,
);
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,19 +1,13 @@
class GetOccupancyHeatMapParam { class GetOccupancyHeatMapParam {
final DateTime year; final DateTime year;
final String communityId; final String spaceUuid;
final String spaceId;
const GetOccupancyHeatMapParam({ const GetOccupancyHeatMapParam({
required this.year, required this.year,
required this.communityId, required this.spaceUuid,
required this.spaceId,
}); });
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {'year': year.year};
'year': year.toIso8601String(),
'communityId': communityId,
'spaceId': spaceId,
};
} }
} }

View File

@ -1,25 +0,0 @@
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
class FakeOccupancyHeatMapService implements OccupancyHeatMapService {
@override
Future<List<OccupancyHeatMapModel>> load(GetOccupancyHeatMapParam param) {
return Future.delayed(const Duration(milliseconds: 200), () {
final now = DateTime.now();
final startOfYear = DateTime(now.year, 1, 1);
final endOfYear = DateTime(now.year, 12, 31);
final daysInYear = endOfYear.difference(startOfYear).inDays + 1;
final List<OccupancyHeatMapModel> data = List.generate(
daysInYear,
(index) => OccupancyHeatMapModel(
date: startOfYear.add(Duration(days: index)),
occupancy: ((index + 1) * 10) % 100,
),
);
return data;
});
}
}

View File

@ -0,0 +1,35 @@
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
const RemoteOccupancyHeatMapService(this._httpService);
final HTTPService _httpService;
@override
Future<List<OccupancyHeatMapModel>> load(GetOccupancyHeatMapParam param) async {
try {
final response = await _httpService.get(
path: '/occupancy/heat-map/space/${param.spaceUuid}',
showServerMessage: true,
queryParameters: param.toJson(),
expectedResponseModel: (response) {
final json = response as Map<String, dynamic>;
final dailyData = json['data'] as List<dynamic>? ?? <dynamic>[];
final result = dailyData.map(
(json) => OccupancyHeatMapModel.fromJson(json as Map<String, dynamic>),
);
return result.toList();
},
);
return response;
} catch (e) {
throw Exception('Failed to load total energy consumption:');
}
}
}

View File

@ -12,7 +12,7 @@ class AnalyticsErrorWidget extends StatelessWidget {
return Visibility( return Visibility(
visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false), visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false),
child: Text( child: Text(
'$errorMessage ?? "Something went wrong"', errorMessage ?? 'Something went wrong',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: context.textTheme.bodySmall?.copyWith( style: context.textTheme.bodySmall?.copyWith(

View File

@ -26,10 +26,8 @@ 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 ?? valueDescription: event.functionData.valueDescription ?? existingData.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();
@ -61,10 +59,8 @@ class FunctionBloc extends Bloc<FunctionBlocEvent, FunctionBlocState> {
); );
} }
FutureOr<void> _onSelectFunction( FutureOr<void> _onSelectFunction(SelectFunction event, Emitter<FunctionBlocState> emit) {
SelectFunction event, Emitter<FunctionBlocState> emit) {
emit(state.copyWith( emit(state.copyWith(
selectedFunction: event.functionCode, selectedFunction: event.functionCode, selectedOperationName: event.operationName));
selectedOperationName: event.operationName));
} }
} }

View File

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

View File

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

View File

@ -4,11 +4,6 @@ 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,
@ -16,10 +11,6 @@ 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,
}); });
} }
@ -31,10 +22,6 @@ 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,
@ -44,10 +31,6 @@ 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() {
@ -59,10 +42,6 @@ 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,
}; };
} }
@ -75,10 +54,6 @@ 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(),
); );
} }
@ -93,11 +68,7 @@ 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
@ -108,10 +79,6 @@ 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,11 +20,12 @@ 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',
@ -49,9 +50,9 @@ class FlushPresenceDelayFunction extends FlushFunctions {
} }
class FlushSensiReduceFunction extends FlushFunctions { class FlushSensiReduceFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
FlushSensiReduceFunction({ FlushSensiReduceFunction({
required super.deviceId, required super.deviceId,
@ -79,8 +80,8 @@ class FlushSensiReduceFunction extends FlushFunctions {
} }
class FlushNoneDelayFunction extends FlushFunctions { class FlushNoneDelayFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final String unit; final String unit;
FlushNoneDelayFunction({ FlushNoneDelayFunction({
@ -109,9 +110,9 @@ class FlushNoneDelayFunction extends FlushFunctions {
} }
class FlushIlluminanceFunction extends FlushFunctions { class FlushIlluminanceFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
FlushIlluminanceFunction({ FlushIlluminanceFunction({
required super.deviceId, required super.deviceId,
@ -129,7 +130,7 @@ class FlushIlluminanceFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
List<FlushOperationalValue> values = []; List<FlushOperationalValue> values = [];
for (int lux = min.toInt(); lux <= max; lux += step.toInt()) { for (int lux = min; lux <= max; lux += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.IlluminanceIcon, icon: Assets.IlluminanceIcon,
description: "$lux Lux", description: "$lux Lux",
@ -141,9 +142,9 @@ class FlushIlluminanceFunction extends FlushFunctions {
} }
class FlushOccurDistReduceFunction extends FlushFunctions { class FlushOccurDistReduceFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
FlushOccurDistReduceFunction({ FlushOccurDistReduceFunction({
required super.deviceId, required super.deviceId,
@ -172,9 +173,9 @@ class FlushOccurDistReduceFunction extends FlushFunctions {
// ==== then functions ==== // ==== then functions ====
class FlushSensitivityFunction extends FlushFunctions { class FlushSensitivityFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
FlushSensitivityFunction({ FlushSensitivityFunction({
required super.deviceId, required super.deviceId,
@ -202,9 +203,9 @@ class FlushSensitivityFunction extends FlushFunctions {
} }
class FlushNearDetectionFunction extends FlushFunctions { class FlushNearDetectionFunction extends FlushFunctions {
final double min; final int min;
final double max; final double max;
final double step; final int step;
final String unit; final String unit;
FlushNearDetectionFunction({ FlushNearDetectionFunction({
@ -224,7 +225,7 @@ class FlushNearDetectionFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min.toDouble(); value <= max; value += step) { for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -236,9 +237,9 @@ class FlushNearDetectionFunction extends FlushFunctions {
} }
class FlushMaxDetectDistFunction extends FlushFunctions { class FlushMaxDetectDistFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
final String unit; final String unit;
FlushMaxDetectDistFunction({ FlushMaxDetectDistFunction({
@ -258,7 +259,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.toInt()) { for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -270,9 +271,9 @@ class FlushMaxDetectDistFunction extends FlushFunctions {
} }
class FlushTargetConfirmTimeFunction extends FlushFunctions { class FlushTargetConfirmTimeFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
final String unit; final String unit;
FlushTargetConfirmTimeFunction({ FlushTargetConfirmTimeFunction({
@ -292,7 +293,7 @@ class FlushTargetConfirmTimeFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min.toDouble(); value <= max; value += step) { for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -304,9 +305,9 @@ class FlushTargetConfirmTimeFunction extends FlushFunctions {
} }
class FlushDisappeDelayFunction extends FlushFunctions { class FlushDisappeDelayFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
final String unit; final String unit;
FlushDisappeDelayFunction({ FlushDisappeDelayFunction({
@ -326,7 +327,7 @@ class FlushDisappeDelayFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min.toDouble(); value <= max; value += step) { for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -338,9 +339,9 @@ class FlushDisappeDelayFunction extends FlushFunctions {
} }
class FlushIndentLevelFunction extends FlushFunctions { class FlushIndentLevelFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
final String unit; final String unit;
FlushIndentLevelFunction({ FlushIndentLevelFunction({
@ -360,7 +361,7 @@ class FlushIndentLevelFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min.toDouble(); value <= max; value += step) { for (var value = min; value <= max; value += step) {
values.add(FlushOperationalValue( values.add(FlushOperationalValue(
icon: Assets.nobodyTime, icon: Assets.nobodyTime,
description: '$value $unit', description: '$value $unit',
@ -372,9 +373,9 @@ class FlushIndentLevelFunction extends FlushFunctions {
} }
class FlushTriggerLevelFunction extends FlushFunctions { class FlushTriggerLevelFunction extends FlushFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
final String unit; final String unit;
FlushTriggerLevelFunction({ FlushTriggerLevelFunction({
@ -394,7 +395,7 @@ class FlushTriggerLevelFunction extends FlushFunctions {
@override @override
List<FlushOperationalValue> getOperationalValues() { List<FlushOperationalValue> getOperationalValues() {
final values = <FlushOperationalValue>[]; final values = <FlushOperationalValue>[];
for (var value = min.toDouble(); value <= max; value += step) { for (var value = min; 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,17 +20,18 @@ 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,
}) : super( }) : min = 0,
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 [
@ -54,11 +55,13 @@ 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,
}) : super( }) : min = 0,
super(
code: 'switch_1', code: 'switch_1',
operationName: 'Switch', operationName: 'Switch',
icon: Assets.assetsAcPower, icon: Assets.assetsAcPower,
@ -101,11 +104,12 @@ 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,10 +13,6 @@ 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();
@ -24,13 +20,9 @@ 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 double min; final int max;
@override final int step;
final double max;
@override
final double step;
@override
final String unit; final String unit;
FarDetectionFunction( FarDetectionFunction(
@ -49,7 +41,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.toInt()) { for (var value = min; value <= max; value += step) {
values.add(WpsOperationalValue( values.add(WpsOperationalValue(
icon: Assets.currentDistanceIcon, icon: Assets.currentDistanceIcon,
description: '$value $unit', description: '$value $unit',
@ -62,9 +54,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 double min; final int min;
final double max; final int max;
final double step; final int step;
final String unit; final String unit;
PresenceTimeFunction( PresenceTimeFunction(
@ -94,9 +86,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 double min; final int min;
final double max; final int max;
final double step; final int step;
MotionSensitivityFunction( MotionSensitivityFunction(
{required super.deviceId, required super.deviceName, required type}) {required super.deviceId, required super.deviceName, required type})
@ -124,9 +116,9 @@ class MotionSensitivityFunction extends WpsFunctions {
} }
class MotionLessSensitivityFunction extends WpsFunctions { class MotionLessSensitivityFunction extends WpsFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
MotionLessSensitivityFunction( MotionLessSensitivityFunction(
{required super.deviceId, required super.deviceName, required type}) {required super.deviceId, required super.deviceName, required type})
@ -179,8 +171,8 @@ class IndicatorFunction extends WpsFunctions {
} }
class NoOneTimeFunction extends WpsFunctions { class NoOneTimeFunction extends WpsFunctions {
final double min; final int min;
final double max; final int max;
final String unit; final String unit;
NoOneTimeFunction( NoOneTimeFunction(
@ -233,9 +225,9 @@ class PresenceStateFunction extends WpsFunctions {
} }
class CurrentDistanceFunction extends WpsFunctions { class CurrentDistanceFunction extends WpsFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
CurrentDistanceFunction( CurrentDistanceFunction(
{required super.deviceId, required super.deviceName, required type}) {required super.deviceId, required super.deviceName, required type})
@ -252,10 +244,11 @@ class CurrentDistanceFunction extends WpsFunctions {
@override @override
List<WpsOperationalValue> getOperationalValues() { List<WpsOperationalValue> getOperationalValues() {
List<WpsOperationalValue> values = []; List<WpsOperationalValue> values = [];
for (int cm = min.toInt(); cm <= max; cm += step.toInt()) { for (int cm = min; cm <= max; cm += step) {
values.add(WpsOperationalValue( values.add(WpsOperationalValue(
icon: Assets.assetsTempreture, icon: Assets.assetsTempreture,
description: "${cm}CM", description: "${cm}CM",
value: cm, value: cm,
)); ));
} }
@ -264,9 +257,9 @@ class CurrentDistanceFunction extends WpsFunctions {
} }
class IlluminanceValueFunction extends WpsFunctions { class IlluminanceValueFunction extends WpsFunctions {
final double min; final int min;
final double max; final int max;
final double step; final int step;
IlluminanceValueFunction({ IlluminanceValueFunction({
required super.deviceId, required super.deviceId,
@ -284,7 +277,7 @@ class IlluminanceValueFunction extends WpsFunctions {
@override @override
List<WpsOperationalValue> getOperationalValues() { List<WpsOperationalValue> getOperationalValues() {
List<WpsOperationalValue> values = []; List<WpsOperationalValue> values = [];
for (int lux = min.toInt(); lux <= max; lux += step.toInt()) { for (int lux = min; lux <= max; lux += step) {
values.add(WpsOperationalValue( values.add(WpsOperationalValue(
icon: Assets.IlluminanceIcon, icon: Assets.IlluminanceIcon,
description: "$lux Lux", description: "$lux Lux",

View File

@ -1,297 +0,0 @@
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,7 +7,6 @@ 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';
@ -77,20 +76,24 @@ class ACHelper {
context: context, context: context,
acFunctions: acFunctions, acFunctions: acFunctions,
device: device, device: device,
onFunctionSelected: onFunctionSelected: (functionCode, operationName) {
(functionCode, operationName) {
RoutineTapFunctionHelper.onTapFunction( RoutineTapFunctionHelper.onTapFunction(
context, context,
functionCode: functionCode, functionCode: functionCode,
functionOperationName: operationName, functionOperationName: operationName,
functionValueDescription: functionValueDescription:
selectedFunctionData.valueDescription, selectedFunctionData.valueDescription,
deviceUuid: device?.uuid, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [ codesToAddIntoFunctionsWithDefaultValue: [
'temp_set', 'temp_set',
'temp_current', 'temp_current',
], ],
defaultValue: 0); defaultValue: functionCode == 'temp_set'
? 200
: functionCode == 'temp_current'
? -100
: 0,
);
}, },
), ),
), ),
@ -203,61 +206,27 @@ class ACHelper {
required String operationName, required String operationName,
bool? removeComparators, bool? removeComparators,
}) { }) {
final selectedFn = final initialVal = selectedFunction == 'temp_set' ? 200 : -100;
acFunctions.firstWhere((f) => f.code == selectedFunction);
if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') { if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') {
// Convert stored integer value to display value final initialValue = selectedFunctionData?.value ?? initialVal;
final displayValue = return _buildTemperatureSelector(
(selectedFunctionData?.value ?? selectedFn.min ?? 0) / 10; context: context,
final minValue = selectedFn.min! / 10; initialValue: initialValue,
final maxValue = selectedFn.max! / 10; selectCode: selectedFunction,
return CustomRoutinesTextbox(
withSpecialChar: true,
dividendOfRange: maxValue,
currentCondition: selectedFunctionData?.condition, currentCondition: selectedFunctionData?.condition,
dialogType: selectedFn.type, device: device,
sliderRange: (minValue, maxValue), operationName: operationName,
displayedValue: displayValue.toStringAsFixed(1), selectedFunctionData: selectedFunctionData,
initialValue: displayValue.toDouble(), removeComparators: removeComparators,
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: selectedFn.getOperationalValues(), values: values,
selectedValue: selectedFunctionData?.value, selectedValue: selectedFunctionData?.value,
device: device, device: device,
operationName: operationName, operationName: operationName,
@ -266,151 +235,150 @@ 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: isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
// conditions.map((c) => c == (currentCondition ?? "==")).toList(), children: conditions.map((c) => Text(c)).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,
@ -446,9 +414,7 @@ class ACHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@ -464,8 +430,7 @@ class ACHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -41,8 +41,7 @@ class _CeilingSensorDialogState extends State<CeilingSensorDialog> {
void initState() { void initState() {
super.initState(); super.initState();
_cpsFunctions = _cpsFunctions = widget.functions.whereType<CpsFunctions>().where((function) {
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';
} }
@ -150,7 +149,6 @@ 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,7 +4,6 @@ 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';
@ -17,7 +16,6 @@ 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,
}); });
@ -28,16 +26,13 @@ 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 CustomRoutinesTextbox( return SliderValueSelector(
withSpecialChar: false,
currentCondition: selectedFunctionData.condition, currentCondition: selectedFunctionData.condition,
dialogType: dialogType, dialogType: dialogType,
sliderRange: sliderRange: CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode),
CpsSliderHelpers.sliderRange(selectedFunctionData.functionCode),
displayedValue: CpsSliderHelpers.displayText( displayedValue: CpsSliderHelpers.displayText(
value: selectedFunctionData.value, value: selectedFunctionData.value,
functionCode: selectedFunctionData.functionCode, functionCode: selectedFunctionData.functionCode,
@ -55,7 +50,7 @@ class CpsDialogSliderSelector extends StatelessWidget {
), ),
), ),
), ),
onTextChanged: (value) => context.read<FunctionBloc>().add( onSliderChanged: (value) => context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
@ -69,7 +64,6 @@ class CpsDialogSliderSelector extends StatelessWidget {
dividendOfRange: CpsSliderHelpers.dividendOfRange( dividendOfRange: CpsSliderHelpers.dividendOfRange(
selectedFunctionData.functionCode, selectedFunctionData.functionCode,
), ),
stepIncreaseAmount: step,
); );
} }
} }

View File

@ -34,33 +34,30 @@ class CpsFunctionsList extends StatelessWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final function = cpsFunctions[index]; final function = cpsFunctions[index];
return RoutineDialogFunctionListTile( return RoutineDialogFunctionListTile(
iconPath: function.icon, iconPath: function.icon,
operationName: function.operationName, operationName: function.operationName,
onTap: () { onTap: () => RoutineTapFunctionHelper.onTapFunction(
RoutineTapFunctionHelper.onTapFunction( context,
context, functionCode: function.code,
step: function.step, functionOperationName: function.operationName,
functionCode: function.code, functionValueDescription: selectedFunctionData?.valueDescription,
functionOperationName: function.operationName, deviceUuid: device?.uuid,
functionValueDescription: codesToAddIntoFunctionsWithDefaultValue: [
selectedFunctionData?.valueDescription, 'static_max_dis',
deviceUuid: device?.uuid, 'presence_reference',
codesToAddIntoFunctionsWithDefaultValue: [ 'moving_reference',
'static_max_dis', 'perceptual_boundary',
'presence_reference', 'moving_boundary',
'moving_reference', 'moving_rigger_time',
'perceptual_boundary', 'moving_static_time',
'moving_boundary', 'none_body_time',
'moving_rigger_time', 'moving_max_dis',
'moving_static_time', 'moving_range',
'none_body_time', 'presence_range',
'moving_max_dis', if (dialogType == "IF") 'sensitivity',
'moving_range', ],
'presence_range', ),
if (dialogType == "IF") 'sensitivity', );
],
);
});
}, },
), ),
); );

View File

@ -1,5 +1,4 @@
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';
@ -22,20 +21,22 @@ class FlushOperationalValuesList extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
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) => _buildValueItem(context, values[index]), itemBuilder: (context, 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),
], ],
@ -43,6 +44,9 @@ 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),
@ -56,4 +60,6 @@ class FlushOperationalValuesList extends StatelessWidget {
groupValue: selectedValue, groupValue: selectedValue,
onChanged: (_) => onSelect(value)); onChanged: (_) => onSelect(value));
} }
} }

View File

@ -5,7 +5,6 @@ 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';
@ -67,8 +66,7 @@ class FlushValueSelectorWidget extends StatelessWidget {
if (isDistanceDetection) { if (isDistanceDetection) {
initialValue = initialValue / 100; initialValue = initialValue / 100;
} }
return CustomRoutinesTextbox( return SliderValueSelector(
withSpecialChar: true,
currentCondition: functionData.condition, currentCondition: functionData.condition,
dialogType: dialogType, dialogType: dialogType,
sliderRange: sliderRange, sliderRange: sliderRange,
@ -85,7 +83,7 @@ class FlushValueSelectorWidget extends StatelessWidget {
), ),
), ),
), ),
onTextChanged: (value) { onSliderChanged: (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;
@ -104,7 +102,6 @@ class FlushValueSelectorWidget extends StatelessWidget {
}, },
unit: _unit, unit: _unit,
dividendOfRange: stepSize, dividendOfRange: stepSize,
stepIncreaseAmount: stepSize,
); );
} }

View File

@ -8,7 +8,6 @@ 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,15 +87,14 @@ class OneGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () => RoutineTapFunctionHelper onTap: () =>
.onTapFunction( RoutineTapFunctionHelper.onTapFunction(
context, context,
functionCode: function.code, functionCode: function.code,
functionOperationName: functionOperationName:
function.operationName, function.operationName,
functionValueDescription: functionValueDescription:
selectedFunctionData selectedFunctionData.valueDescription,
.valueDescription,
deviceUuid: device?.uuid, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [ codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1', 'countdown_1',
@ -109,16 +108,14 @@ class OneGangSwitchHelper {
if (selectedFunction != null) if (selectedFunction != null)
Expanded( Expanded(
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: removeComparetors: removeComparetors,
selectedOperationName ?? '', ),
removeComparetors: removeComparetors,
dialogType: dialogType),
), ),
], ],
), ),
@ -175,7 +172,6 @@ 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;
@ -188,7 +184,6 @@ class OneGangSwitchHelper {
operationName: operationName, operationName: operationName,
selectedFunctionData: selectedFunctionData, selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
dialogType: dialogType,
); );
} }
final selectedFn = acFunctions.firstWhere( final selectedFn = acFunctions.firstWhere(
@ -221,18 +216,93 @@ 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, dialogType!), selectedFunctionData, selectCode),
], ],
); );
} }
/// 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,
@ -240,47 +310,38 @@ class OneGangSwitchHelper {
String operationName, String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
String selectCode, String selectCode,
String dialogType,
) { ) {
return CustomRoutinesTextbox( const twelveHoursInSeconds = 43200.0;
withSpecialChar: false, final operationalValues = SwitchOperationalValue(
currentCondition: selectedFunctionData?.condition, icon: '',
dialogType: dialogType, description: "sec",
sliderRange: (0, 43200), value: 0.0,
displayedValue: (initialValue ?? 0).toString(), minValue: 0,
initialValue: (initialValue ?? 0).toString(), maxValue: twelveHoursInSeconds,
onConditionChanged: (condition) { stepValue: 1,
);
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,
condition: condition, value: value,
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,
); );
} }
@ -316,9 +377,7 @@ class OneGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@ -334,8 +393,7 @@ class OneGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.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,21 +86,20 @@ class ThreeGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () => RoutineTapFunctionHelper onTap: () =>
.onTapFunction( RoutineTapFunctionHelper.onTapFunction(
context, context,
functionCode: function.code, functionCode: function.code,
functionOperationName: functionOperationName:
function.operationName, function.operationName,
functionValueDescription: functionValueDescription:
selectedFunctionData selectedFunctionData.valueDescription,
.valueDescription,
deviceUuid: device?.uuid, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: codesToAddIntoFunctionsWithDefaultValue: [
function.code 'countdown_1',
.startsWith('countdown') 'countdown_2',
? [function.code] 'countdown_3',
: [], ],
), ),
); );
}, },
@ -110,16 +109,14 @@ class ThreeGangSwitchHelper {
if (selectedFunction != null) if (selectedFunction != null)
Expanded( Expanded(
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: removeComparetors: removeComparetors,
selectedOperationName ?? '', ),
removeComparetors: removeComparetors,
dialogType: dialogType),
), ),
], ],
), ),
@ -136,6 +133,14 @@ 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,
@ -168,26 +173,24 @@ 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' ||
selectedFunction == 'countdown_3') { selectedFunction == 'countdown_3') {
final initialValue = selectedFunctionData?.value ?? 0; final initialValue = selectedFunctionData?.value ?? 0;
return _buildTemperatureSelector( return _buildTemperatureSelector(
context: context, context: context,
initialValue: initialValue, initialValue: initialValue,
selectCode: selectedFunction, selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition, currentCondition: selectedFunctionData?.condition,
device: device, device: device,
operationName: operationName, operationName: operationName,
selectedFunctionData: selectedFunctionData, selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
dialogType: dialogType); );
} }
final selectedFn = final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@ -210,18 +213,93 @@ 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, dialogType), selectedFunctionData, selectCode),
], ],
); );
} }
/// 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,
@ -229,47 +307,38 @@ class ThreeGangSwitchHelper {
String operationName, String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
String selectCode, String selectCode,
String dialogType,
) { ) {
return CustomRoutinesTextbox( const twelveHoursInSeconds = 43200.0;
withSpecialChar: true, final operationalValues = SwitchOperationalValue(
currentCondition: selectedFunctionData?.condition, icon: '',
dialogType: dialogType, description: "sec",
sliderRange: (0, 43200), value: 0.0,
displayedValue: (initialValue ?? 0).toString(), minValue: 0,
initialValue: (initialValue ?? 0).toString(), maxValue: twelveHoursInSeconds,
onConditionChanged: (condition) { stepValue: 1,
);
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,
condition: condition, value: value,
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,
); );
} }
@ -305,9 +374,7 @@ class ThreeGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@ -323,8 +390,7 @@ class ThreeGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.valueDescription,
selectedFunctionData?.valueDescription,
), ),
), ),
); );

View File

@ -8,7 +8,6 @@ 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';
@ -87,15 +86,14 @@ class TwoGangSwitchHelper {
size: 16, size: 16,
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () => RoutineTapFunctionHelper onTap: () =>
.onTapFunction( RoutineTapFunctionHelper.onTapFunction(
context, context,
functionCode: function.code, functionCode: function.code,
functionOperationName: functionOperationName:
function.operationName, function.operationName,
functionValueDescription: functionValueDescription:
selectedFunctionData selectedFunctionData.valueDescription,
.valueDescription,
deviceUuid: device?.uuid, deviceUuid: device?.uuid,
codesToAddIntoFunctionsWithDefaultValue: [ codesToAddIntoFunctionsWithDefaultValue: [
'countdown_1', 'countdown_1',
@ -117,7 +115,6 @@ class TwoGangSwitchHelper {
device: device, device: device,
operationName: selectedOperationName ?? '', operationName: selectedOperationName ?? '',
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
dialogType: dialogType,
), ),
), ),
], ],
@ -175,25 +172,22 @@ 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' || if (selectedFunction == 'countdown_1' || selectedFunction == 'countdown_2') {
selectedFunction == 'countdown_2') {
final initialValue = selectedFunctionData?.value ?? 0; final initialValue = selectedFunctionData?.value ?? 0;
return _buildTemperatureSelector( return _buildTemperatureSelector(
context: context, context: context,
initialValue: initialValue, initialValue: initialValue,
selectCode: selectedFunction, selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition, currentCondition: selectedFunctionData?.condition,
device: device, device: device,
operationName: operationName, operationName: operationName,
selectedFunctionData: selectedFunctionData, selectedFunctionData: selectedFunctionData,
removeComparetors: removeComparetors, removeComparetors: removeComparetors,
dialogType: dialogType); );
} }
final selectedFn = final selectedFn = switchFunctions.firstWhere((f) => f.code == selectedFunction);
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList( return _buildOperationalValuesList(
@ -216,13 +210,25 @@ 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, dialogType!), selectedFunctionData, selectCode),
], ],
); );
} }
@ -263,8 +269,7 @@ class TwoGangSwitchHelper {
minHeight: 40.0, minHeight: 40.0,
minWidth: 40.0, minWidth: 40.0,
), ),
isSelected: isSelected: conditions.map((c) => c == (currentCondition ?? "==")).toList(),
conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: conditions.map((c) => Text(c)).toList(), children: conditions.map((c) => Text(c)).toList(),
); );
} }
@ -299,48 +304,38 @@ class TwoGangSwitchHelper {
String operationName, String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
String selectCode, String selectCode,
String dialogType,
) { ) {
return CustomRoutinesTextbox( const twelveHoursInSeconds = 43200.0;
withSpecialChar: true, final operationalValues = SwitchOperationalValue(
currentCondition: selectedFunctionData?.condition, icon: '',
dialogType: dialogType, description: "sec",
sliderRange: (0, 43200), value: 0.0,
displayedValue: (initialValue ?? 0).toString(), minValue: 0,
initialValue: (initialValue ?? 0).toString(), maxValue: twelveHoursInSeconds,
onConditionChanged: (condition) { stepValue: 1,
);
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,
condition: condition, value: value,
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,
); );
} }
@ -376,9 +371,7 @@ class TwoGangSwitchHelper {
style: context.textTheme.bodyMedium, style: context.textTheme.bodyMedium,
), ),
trailing: Icon( trailing: Icon(
isSelected isSelected ? Icons.radio_button_checked : Icons.radio_button_unchecked,
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24, size: 24,
color: isSelected color: isSelected
? ColorsManager.primaryColorWithOpacity ? ColorsManager.primaryColorWithOpacity
@ -394,8 +387,7 @@ class TwoGangSwitchHelper {
operationName: operationName, operationName: operationName,
value: value.value, value: value.value,
condition: selectedFunctionData?.condition, condition: selectedFunctionData?.condition,
valueDescription: valueDescription: selectedFunctionData?.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,13 +27,11 @@ class WpsValueSelectorWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final selectedFn = final selectedFn = wpsFunctions.firstWhere((f) => f.code == selectedFunction);
wpsFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues(); final values = selectedFn.getOperationalValues();
if (_isSliderFunction(selectedFunction)) { if (_isSliderFunction(selectedFunction)) {
return CustomRoutinesTextbox( return SliderValueSelector(
withSpecialChar: false,
currentCondition: functionData.condition, currentCondition: functionData.condition,
dialogType: dialogType, dialogType: dialogType,
sliderRange: sliderRange, sliderRange: sliderRange,
@ -50,7 +48,7 @@ class WpsValueSelectorWidget extends StatelessWidget {
), ),
), ),
), ),
onTextChanged: (value) => context.read<FunctionBloc>().add( onSliderChanged: (value) => context.read<FunctionBloc>().add(
AddFunction( AddFunction(
functionData: DeviceFunctionData( functionData: DeviceFunctionData(
entityId: device?.uuid ?? '', entityId: device?.uuid ?? '',
@ -63,7 +61,6 @@ class WpsValueSelectorWidget extends StatelessWidget {
), ),
unit: _unit, unit: _unit,
dividendOfRange: 1, dividendOfRange: 1,
stepIncreaseAmount: _steps,
); );
} }
@ -102,10 +99,4 @@ 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,7 +176,6 @@ 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,24 +2,25 @@ 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,
}); });
@ -38,6 +39,22 @@ 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,
@ -45,7 +62,6 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget {
selectedFn.operationName, selectedFn.operationName,
functionData, functionData,
selectedFunction, selectedFunction,
dialogType
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
], ],
@ -74,6 +90,28 @@ 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,
@ -81,47 +119,78 @@ class WaterHeaterValueSelectorWidget extends StatelessWidget {
String operationName, String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
String selectCode, String selectCode,
String dialogType,
) { ) {
return CustomRoutinesTextbox( const twelveHoursInSeconds = 43200.0;
withSpecialChar: false, final operationalValues = SwitchOperationalValue(
currentCondition: selectedFunctionData?.condition, icon: '',
dialogType: dialogType, description: "sec",
sliderRange: (0, 43200), value: 0.0,
displayedValue: (initialValue ?? 0).toString(), minValue: 0,
initialValue: (initialValue ?? 0).toString(), maxValue: twelveHoursInSeconds,
onConditionChanged: (condition) { stepValue: 1,
);
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: condition, value: value,
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(),
); );
} }
} }