fixes unique id for card drag

This commit is contained in:
ashrafzarkanisala
2024-11-24 01:10:31 +03:00
parent 590a41ff83
commit 5e94de5d78
17 changed files with 905 additions and 712 deletions

View File

@ -105,6 +105,7 @@ class AllDevicesModel {
this.productName, this.productName,
this.spaces, this.spaces,
}); });
AllDevicesModel.fromJson(Map<String, dynamic> json) { AllDevicesModel.fromJson(Map<String, dynamic> json) {
room = (json['room'] != null && (json['room'] is Map)) room = (json['room'] != null && (json['room'] is Map))
? DevicesModelRoom.fromJson(json['room']) ? DevicesModelRoom.fromJson(json['room'])
@ -138,6 +139,7 @@ class AllDevicesModel {
updateTime = int.tryParse(json['updateTime']?.toString() ?? ''); updateTime = int.tryParse(json['updateTime']?.toString() ?? '');
uuid = json['uuid']?.toString(); uuid = json['uuid']?.toString();
batteryLevel = int.tryParse(json['battery']?.toString() ?? ''); batteryLevel = int.tryParse(json['battery']?.toString() ?? '');
productName = json['productName']?.toString(); productName = json['productName']?.toString();
if (json['spaces'] != null && json['spaces'] is List) { if (json['spaces'] != null && json['spaces'] is List) {
spaces = (json['spaces'] as List) spaces = (json['spaces'] as List)

View File

@ -16,9 +16,9 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
on<AddToThenContainer>(_onAddToThenContainer); on<AddToThenContainer>(_onAddToThenContainer);
on<LoadScenes>(_onLoadScenes); on<LoadScenes>(_onLoadScenes);
on<LoadAutomation>(_onLoadAutomation); on<LoadAutomation>(_onLoadAutomation);
on<AddFunctionToRoutine>(_onAddFunction); on<AddFunctionToRoutine>(_onAddFunctionsToRoutine);
on<RemoveFunction>(_onRemoveFunction); // on<RemoveFunction>(_onRemoveFunction);
on<ClearFunctions>(_onClearFunctions); // on<ClearFunctions>(_onClearFunctions);
} }
void _onAddToIfContainer(AddToIfContainer event, Emitter<RoutineState> emit) { void _onAddToIfContainer(AddToIfContainer event, Emitter<RoutineState> emit) {
@ -38,24 +38,33 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// } // }
} }
void _onAddFunction(AddFunctionToRoutine event, Emitter<RoutineState> emit) { void _onAddFunctionsToRoutine(
final functions = List<DeviceFunctionData>.from(state.selectedFunctions); AddFunctionToRoutine event, Emitter<RoutineState> emit) {
functions.add(event.function); debugPrint(event.uniqueCustomId.toString());
debugPrint("******" + functions.toString()); debugPrint(event.functions.toString());
emit(state.copyWith(selectedFunctions: functions)); final currentSelectedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) {
currentSelectedFunctions[event.uniqueCustomId]!.addAll(event.functions);
} else {
currentSelectedFunctions[event.uniqueCustomId] = event.functions;
}
emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
} }
void _onRemoveFunction(RemoveFunction event, Emitter<RoutineState> emit) { // void _onRemoveFunction(RemoveFunction event, Emitter<RoutineState> emit) {
final functions = List<DeviceFunctionData>.from(state.selectedFunctions) // final functions = List<DeviceFunctionData>.from(state.selectedFunctions)
..removeWhere((f) => // ..removeWhere((f) =>
f.functionCode == event.function.functionCode && // f.functionCode == event.function.functionCode &&
f.value == event.function.value); // f.value == event.function.value);
emit(state.copyWith(selectedFunctions: functions)); // emit(state.copyWith(selectedFunctions: functions));
} // }
void _onClearFunctions(ClearFunctions event, Emitter<RoutineState> emit) { // void _onClearFunctions(ClearFunctions event, Emitter<RoutineState> emit) {
emit(state.copyWith(selectedFunctions: [])); // emit(state.copyWith(selectedFunctions: []));
} // }
// bool _isDuplicate( // bool _isDuplicate(
// List<Map<String, dynamic>> items, Map<String, dynamic> newItem) { // List<Map<String, dynamic>> items, Map<String, dynamic> newItem) {

View File

@ -44,10 +44,11 @@ class LoadAutomation extends RoutineEvent {
} }
class AddFunctionToRoutine extends RoutineEvent { class AddFunctionToRoutine extends RoutineEvent {
final DeviceFunctionData function; final List<DeviceFunctionData> functions;
const AddFunctionToRoutine(this.function); final String uniqueCustomId;
const AddFunctionToRoutine(this.functions, this.uniqueCustomId);
@override @override
List<Object> get props => [function]; List<Object> get props => [functions];
} }
class RemoveFunction extends RoutineEvent { class RemoveFunction extends RoutineEvent {

View File

@ -6,7 +6,7 @@ class RoutineState extends Equatable {
final List<Map<String, String>> availableCards; final List<Map<String, String>> availableCards;
final List<ScenesModel> scenes; final List<ScenesModel> scenes;
final List<ScenesModel> automations; final List<ScenesModel> automations;
final List<DeviceFunctionData> selectedFunctions; final Map<String, List<DeviceFunctionData>> selectedFunctions;
final bool isLoading; final bool isLoading;
final String? errorMessage; final String? errorMessage;
@ -16,7 +16,7 @@ class RoutineState extends Equatable {
this.availableCards = const [], this.availableCards = const [],
this.scenes = const [], this.scenes = const [],
this.automations = const [], this.automations = const [],
this.selectedFunctions = const [], this.selectedFunctions = const {},
this.isLoading = false, this.isLoading = false,
this.errorMessage, this.errorMessage,
}); });
@ -26,7 +26,7 @@ class RoutineState extends Equatable {
List<Map<String, dynamic>>? thenItems, List<Map<String, dynamic>>? thenItems,
List<ScenesModel>? scenes, List<ScenesModel>? scenes,
List<ScenesModel>? automations, List<ScenesModel>? automations,
List<DeviceFunctionData>? selectedFunctions, Map<String, List<DeviceFunctionData>>? selectedFunctions,
bool? isLoading, bool? isLoading,
String? errorMessage, String? errorMessage,
}) { }) {

View File

@ -18,6 +18,7 @@ class ACHelper {
List<DeviceFunction> functions, List<DeviceFunction> functions,
AllDevicesModel? device, AllDevicesModel? device,
List<DeviceFunctionData>? deviceSelectedFunctions, List<DeviceFunctionData>? deviceSelectedFunctions,
String uniqueCustomId,
) async { ) async {
List<ACFunction> acFunctions = functions.whereType<ACFunction>().toList(); List<ACFunction> acFunctions = functions.whereType<ACFunction>().toList();
@ -95,13 +96,14 @@ class ACHelper {
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) { // for (var function in state.addedFunctions) {
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
AddFunctionToRoutine( AddFunctionToRoutine(
function, state.addedFunctions,
), uniqueCustomId,
); ),
} );
//}
// Return the device data to be added to the container // Return the device data to be added to the container
Navigator.pop(context, { Navigator.pop(context, {
'deviceId': functions.first.deviceId, 'deviceId': functions.first.deviceId,
@ -229,11 +231,23 @@ class ACHelper {
selectedFunctionData, selectedFunctionData,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildTemperatureDisplay(context, initialValue, device, operationName, _buildTemperatureDisplay(
selectedFunctionData, selectCode), context,
initialValue,
device,
operationName,
selectedFunctionData,
selectCode,
),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildTemperatureSlider(context, initialValue, device, operationName, _buildTemperatureSlider(
selectedFunctionData, selectCode), context,
initialValue,
device,
operationName,
selectedFunctionData,
selectCode,
),
], ],
); );
} }
@ -246,6 +260,7 @@ class ACHelper {
AllDevicesModel? device, AllDevicesModel? device,
String operationName, String operationName,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged, // Function(String) onConditionChanged,
) { ) {
final conditions = ["<", "==", ">"]; final conditions = ["<", "==", ">"];
@ -282,12 +297,13 @@ class ACHelper {
/// 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,
) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -342,6 +358,7 @@ class ACHelper {
required String operationName, required String operationName,
required String selectCode, required String selectCode,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
// required Function(dynamic) onValueChanged, // required Function(dynamic) onValueChanged,
}) { }) {
return ListView.builder( return ListView.builder(

View File

@ -39,24 +39,28 @@ class DeviceDialogHelper {
List<DeviceFunction> functions, List<DeviceFunction> functions,
) async { ) async {
final routineBloc = context.read<RoutineBloc>(); final routineBloc = context.read<RoutineBloc>();
final deviceSelectedFunctions = routineBloc.state.selectedFunctions final deviceSelectedFunctions =
.where((f) => f.entityId == data['deviceId']) routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? [];
.toList();
switch (productType) { switch (productType) {
case 'AC': case 'AC':
return ACHelper.showACFunctionsDialog( return ACHelper.showACFunctionsDialog(context, functions,
context, functions, data['device'], deviceSelectedFunctions); data['device'], deviceSelectedFunctions, data['uniqueCustomId']);
case '1G': case '1G':
return OneGangSwitchHelper.showSwitchFunctionsDialog( return OneGangSwitchHelper.showSwitchFunctionsDialog(context, functions,
context, functions, data['device'], deviceSelectedFunctions); data['device'], deviceSelectedFunctions, data['uniqueCustomId']);
case '2G': case '2G':
return TwoGangSwitchHelper.showSwitchFunctionsDialog( return TwoGangSwitchHelper.showSwitchFunctionsDialog(context, functions,
context, functions); data['device'], deviceSelectedFunctions, data['uniqueCustomId']);
case '3G': case '3G':
return ThreeGangSwitchHelper.showSwitchFunctionsDialog( return ThreeGangSwitchHelper.showSwitchFunctionsDialog(
context, functions); context,
functions,
data['device'],
deviceSelectedFunctions,
data['uniqueCustomId'],
);
default: default:
return null; return null;
} }

View File

@ -4,11 +4,9 @@ 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/routiens/bloc/functions_bloc/functions_bloc_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart';
import 'package:syncrow_web/pages/routiens/models/ac/ac_operational_value.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -20,8 +18,10 @@ class OneGangSwitchHelper {
List<DeviceFunction> functions, List<DeviceFunction> functions,
AllDevicesModel? device, AllDevicesModel? device,
List<DeviceFunctionData>? deviceSelectedFunctions, List<DeviceFunctionData>? deviceSelectedFunctions,
String uniqueCustomId,
) async { ) async {
List<ACFunction> acFunctions = functions.whereType<ACFunction>().toList(); List<BaseSwitchFunction> acFunctions =
functions.whereType<BaseSwitchFunction>().toList();
return showDialog<Map<String, dynamic>?>( return showDialog<Map<String, dynamic>?>(
context: context, context: context,
@ -107,30 +107,6 @@ class OneGangSwitchHelper {
operationName: selectedOperationName ?? '', operationName: selectedOperationName ?? '',
), ),
), ),
// ValueListenableBuilder<String?>(
// valueListenable: selectedFunctionNotifier,
// builder: (context, selectedFunction, _) {
// final selectedFn =
// switchFunctions.firstWhere(
// (f) => f.code == selectedFunction,
// );
// return Expanded(
// child: selectedFn
// is OneGangCountdownFunction
// ? _buildCountDownSelector(
// context,
// selectedValueNotifier,
// selectedConditionNotifier,
// selectedConditionsNotifier,
// )
// : _buildOperationalValuesList(
// context,
// selectedFn as BaseSwitchFunction,
// selectedValueNotifier,
// ),
// );
// },
// ),
], ],
), ),
), ),
@ -146,13 +122,20 @@ class OneGangSwitchHelper {
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) { // for (var function in state.addedFunctions) {
context.read<RoutineBloc>().add( // context.read<RoutineBloc>().add(
AddFunctionToRoutine( // AddFunctionToRoutine(
function, // function,
), // uniqueCustomId,
); // ),
} // );
// }
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
uniqueCustomId,
),
);
// Return the device data to be added to the container // Return the device data to be added to the container
Navigator.pop(context, { Navigator.pop(context, {
'deviceId': functions.first.deviceId, 'deviceId': functions.first.deviceId,
@ -175,13 +158,13 @@ class OneGangSwitchHelper {
required BuildContext context, required BuildContext context,
required String selectedFunction, required String selectedFunction,
required DeviceFunctionData? selectedFunctionData, required DeviceFunctionData? selectedFunctionData,
required List<ACFunction> acFunctions, required List<BaseSwitchFunction> acFunctions,
AllDevicesModel? device, AllDevicesModel? device,
required String operationName, required String operationName,
}) { }) {
if (selectedFunction == 'countdown_1') { if (selectedFunction == 'countdown_1') {
final initialValue = selectedFunctionData?.value ?? 200; final initialValue = selectedFunctionData?.value ?? 200;
return _buildTemperatureSelector( return _buildCountDownSelector(
context: context, context: context,
initialValue: initialValue, initialValue: initialValue,
selectCode: selectedFunction, selectCode: selectedFunction,
@ -207,7 +190,7 @@ class OneGangSwitchHelper {
); );
} }
static Widget _buildTemperatureSelector({ static Widget _buildCountDownSelector({
required BuildContext context, required BuildContext context,
required dynamic initialValue, required dynamic initialValue,
required String? currentCondition, required String? currentCondition,
@ -228,10 +211,10 @@ class OneGangSwitchHelper {
selectedFunctionData, selectedFunctionData,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildTemperatureDisplay(context, initialValue, device, operationName, _buildCountDownDisplay(context, initialValue, device, operationName,
selectedFunctionData, selectCode), selectedFunctionData, selectCode),
const SizedBox(height: 20), const SizedBox(height: 20),
_buildTemperatureSlider(context, initialValue, device, operationName, _buildCountDownSlider(context, initialValue, device, operationName,
selectedFunctionData, selectCode), selectedFunctionData, selectCode),
], ],
); );
@ -280,7 +263,7 @@ class OneGangSwitchHelper {
} }
/// Build temperature display for AC functions dialog /// Build temperature display for AC functions dialog
static Widget _buildTemperatureDisplay( static Widget _buildCountDownDisplay(
BuildContext context, BuildContext context,
dynamic initialValue, dynamic initialValue,
AllDevicesModel? device, AllDevicesModel? device,
@ -302,7 +285,7 @@ class OneGangSwitchHelper {
); );
} }
static Widget _buildTemperatureSlider( static Widget _buildCountDownSlider(
BuildContext context, BuildContext context,
dynamic initialValue, dynamic initialValue,
AllDevicesModel? device, AllDevicesModel? device,
@ -310,11 +293,22 @@ class OneGangSwitchHelper {
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
String selectCode, String selectCode,
) { ) {
final operationalValues = SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 43200,
stepValue: 1,
);
return Slider( return Slider(
value: (initialValue ?? 0).toDouble(), value: (initialValue ?? 0).toDouble(),
min: 0, min: operationalValues.minValue?.toDouble() ?? 0.0,
max: 300, max: operationalValues.maxValue?.toDouble() ?? 0.0,
divisions: 300, divisions: (((operationalValues.maxValue ?? 0) -
(operationalValues.minValue ?? 0)) /
(operationalValues.stepValue ?? 1))
.round(),
onChanged: (value) { onChanged: (value) {
context.read<FunctionBloc>().add( context.read<FunctionBloc>().add(
AddFunction( AddFunction(
@ -334,13 +328,12 @@ class OneGangSwitchHelper {
static Widget _buildOperationalValuesList({ static Widget _buildOperationalValuesList({
required BuildContext context, required BuildContext context,
required List<ACOperationalValue> values, required List<SwitchOperationalValue> values,
required dynamic selectedValue, required dynamic selectedValue,
AllDevicesModel? device, AllDevicesModel? device,
required String operationName, required String operationName,
required String selectCode, required String selectCode,
DeviceFunctionData? selectedFunctionData, DeviceFunctionData? selectedFunctionData,
// required Function(dynamic) onValueChanged,
}) { }) {
return ListView.builder( return ListView.builder(
shrinkWrap: false, shrinkWrap: false,
@ -394,102 +387,4 @@ class OneGangSwitchHelper {
}, },
); );
} }
// static Widget _buildCountDownSelector(
// BuildContext context,
// ValueNotifier<dynamic> valueNotifier,
// ValueNotifier<String?> conditionNotifier,
// ValueNotifier<List<bool>> conditionsNotifier,
// ) {
// return ValueListenableBuilder<dynamic>(
// valueListenable: valueNotifier,
// builder: (context, value, _) {
// return Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// ValueListenableBuilder<List<bool>>(
// valueListenable: conditionsNotifier,
// builder: (context, selectedConditions, _) {
// return ToggleButtons(
// onPressed: (int index) {
// final newConditions = List<bool>.filled(3, false);
// newConditions[index] = true;
// conditionsNotifier.value = newConditions;
// conditionNotifier.value = index == 0
// ? "<"
// : index == 1
// ? "=="
// : ">";
// },
// 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: selectedConditions,
// children: const [Text("<"), Text("="), Text(">")],
// );
// },
// ),
// const SizedBox(height: 20),
// Text(
// '${value ?? 0} sec',
// style: Theme.of(context).textTheme.headlineMedium,
// ),
// const SizedBox(height: 20),
// Slider(
// value: (value ?? 0).toDouble(),
// min: 0,
// max: 300,
// divisions: 300,
// onChanged: (newValue) {
// valueNotifier.value = newValue.toInt();
// },
// ),
// ],
// );
// },
// );
// }
// static Widget _buildOperationalValuesList(
// BuildContext context,
// BaseSwitchFunction function,
// ValueNotifier<dynamic> valueNotifier,
// ) {
// final values = function.getOperationalValues();
// return ValueListenableBuilder<dynamic>(
// valueListenable: valueNotifier,
// builder: (context, selectedValue, _) {
// return ListView.builder(
// itemCount: values.length,
// itemBuilder: (context, index) {
// final value = values[index];
// return ListTile(
// leading: SvgPicture.asset(
// value.icon,
// width: 24,
// height: 24,
// ),
// title: Text(
// value.description,
// style: context.textTheme.bodyMedium,
// ),
// trailing: Radio<dynamic>(
// value: value.value,
// groupValue: selectedValue,
// onChanged: (newValue) {
// valueNotifier.value = newValue;
// },
// ),
// );
// },
// );
// },
// );
// }
} }

View File

@ -1,8 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; 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/routiens/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/three_gang_switch/three_gang_switch.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -10,64 +14,60 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
class ThreeGangSwitchHelper { class ThreeGangSwitchHelper {
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog( static Future<Map<String, dynamic>?> showSwitchFunctionsDialog(
BuildContext context, List<DeviceFunction<dynamic>> functions) async { BuildContext context,
List<DeviceFunction<dynamic>> switchFunctions = functions List<DeviceFunction> functions,
.where((f) => AllDevicesModel? device,
f is ThreeGangSwitch1Function || List<DeviceFunctionData>? deviceSelectedFunctions,
f is ThreeGangSwitch2Function || String uniqueCustomId,
f is ThreeGangSwitch3Function || ) async {
f is ThreeGangCountdown1Function || List<BaseSwitchFunction> switchFunctions =
f is ThreeGangCountdown2Function || functions.whereType<BaseSwitchFunction>().toList();
f is ThreeGangCountdown3Function)
.toList();
final selectedFunctionNotifier = ValueNotifier<String?>(null); return showDialog<Map<String, dynamic>?>(
final selectedValueNotifier = ValueNotifier<dynamic>(null);
final selectedConditionNotifier = ValueNotifier<String?>('<');
final selectedConditionsNotifier =
ValueNotifier<List<bool>>([true, false, false]);
await showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return ValueListenableBuilder<String?>( return BlocProvider(
valueListenable: selectedFunctionNotifier, create: (_) => FunctionBloc()
builder: (context, selectedFunction, _) { ..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
return AlertDialog( child: AlertDialog(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: Container( content: BlocBuilder<FunctionBloc, FunctionBlocState>(
width: selectedFunction != null ? 600 : 300, builder: (context, state) {
height: 450, final selectedFunction = state.selectedFunction;
decoration: BoxDecoration( final selectedOperationName = state.selectedOperationName;
color: Colors.white, final selectedFunctionData = state.addedFunctions
borderRadius: BorderRadius.circular(20), .firstWhere((f) => f.functionCode == selectedFunction,
), orElse: () => DeviceFunctionData(
padding: const EdgeInsets.only(top: 20), entityId: '',
child: Column( functionCode: selectedFunction ?? '',
mainAxisSize: MainAxisSize.min, operationName: '',
children: [ value: null,
const DialogHeader('3 Gangs Light Switch Condition'), ));
Expanded( return Container(
child: Row( width: selectedFunction != null ? 600 : 360,
children: [ height: 450,
// Left side: Function list decoration: BoxDecoration(
Expanded( color: Colors.white,
child: ListView.separated( borderRadius: BorderRadius.circular(20),
itemCount: switchFunctions.length, ),
separatorBuilder: (_, __) => const Divider( padding: const EdgeInsets.only(top: 20),
color: ColorsManager.dividerColor, child: Column(
), mainAxisSize: MainAxisSize.min,
itemBuilder: (context, index) { children: [
final function = switchFunctions[index]; const DialogHeader('3 Gangs Light Switch Condition'),
return ValueListenableBuilder<String?>( Expanded(
valueListenable: selectedFunctionNotifier, child: Row(
builder: (context, selectedFunction, _) { children: [
final isSelected = // Left side: Function list
selectedFunction == function.code; Expanded(
child: ListView.separated(
itemCount: switchFunctions.length,
separatorBuilder: (_, __) => const Divider(
color: ColorsManager.dividerColor,
),
itemBuilder: (context, index) {
final function = switchFunctions[index];
return ListTile( return ListTile(
tileColor: isSelected
? Colors.grey.shade100
: null,
leading: SvgPicture.asset( leading: SvgPicture.asset(
function.icon, function.icon,
width: 24, width: 24,
@ -83,196 +83,307 @@ class ThreeGangSwitchHelper {
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () { onTap: () {
selectedFunctionNotifier.value = context
function.code; .read<FunctionBloc>()
selectedValueNotifier.value = function .add(SelectFunction(
is ThreeGangCountdown1Function || functionCode: function.code,
function operationName:
is ThreeGangCountdown2Function || function.operationName,
function ));
is ThreeGangCountdown3Function
? 0
: null;
}, },
); );
}, },
); ),
},
),
),
// Right side: Value selector
if (selectedFunction != null)
Expanded(
child: ValueListenableBuilder<dynamic>(
valueListenable: selectedValueNotifier,
builder: (context, selectedValue, _) {
final selectedFn = switchFunctions.firstWhere(
(f) => f.code == selectedFunction,
);
return selectedFn
is ThreeGangCountdown1Function ||
selectedFn
is ThreeGangCountdown2Function ||
selectedFn
is ThreeGangCountdown3Function
? _buildCountDownSelector(
context,
selectedValueNotifier,
selectedConditionNotifier,
selectedConditionsNotifier,
)
: _buildOperationalValuesList(
context,
selectedFn as BaseSwitchFunction,
selectedValueNotifier,
);
},
), ),
), // Right side: Value selector
], if (selectedFunction != null)
), Expanded(
child: _buildValueSelector(
context: context,
selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData,
switchFunctions: switchFunctions,
device: device,
operationName: selectedOperationName ?? '',
),
),
],
),
),
Container(
height: 1,
width: double.infinity,
color: ColorsManager.greyColor,
),
DialogFooter(
onCancel: () {
Navigator.pop(context);
},
onConfirm: state.addedFunctions.isNotEmpty
? () {
/// add the functions to the routine bloc
// for (var function in state.addedFunctions) {
// context.read<RoutineBloc>().add(
// AddFunctionToRoutine(
// function,
// uniqueCustomId,
// ),
// );
// }
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
uniqueCustomId,
),
);
// Return the device data to be added to the container
Navigator.pop(context, {
'deviceId': functions.first.deviceId,
});
}
: null,
isConfirmEnabled: selectedFunction != null,
),
],
), ),
DialogFooter( );
onCancel: () => Navigator.pop(context), },
onConfirm: selectedFunctionNotifier.value != null &&
selectedValueNotifier.value != null
? () {
final selectedFn = switchFunctions.firstWhere(
(f) => f.code == selectedFunctionNotifier.value,
);
final value = selectedValueNotifier.value;
final functionData = DeviceFunctionData(
entityId: selectedFn.deviceId,
functionCode: selectedFn.code,
operationName: selectedFn.operationName,
value: value,
condition: selectedConditionNotifier.value,
valueDescription: selectedFn
is ThreeGangCountdown1Function ||
selectedFn
is ThreeGangCountdown2Function ||
selectedFn
is ThreeGangCountdown3Function
? '${value} sec'
: ((selectedFn as BaseSwitchFunction)
.getOperationalValues()
.firstWhere((v) => v.value == value)
.description),
);
Navigator.pop(
context, {selectedFn.code: functionData});
}
: null,
isConfirmEnabled: selectedFunctionNotifier.value != null,
),
],
),
), ),
); ));
},
);
},
).then((value) {
selectedFunctionNotifier.dispose();
selectedValueNotifier.dispose();
selectedConditionNotifier.dispose();
selectedConditionsNotifier.dispose();
return value;
});
}
static Widget _buildCountDownSelector(
BuildContext context,
ValueNotifier<dynamic> valueNotifier,
ValueNotifier<String?> conditionNotifier,
ValueNotifier<List<bool>> conditionsNotifier,
) {
return ValueListenableBuilder<dynamic>(
valueListenable: valueNotifier,
builder: (context, value, _) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ValueListenableBuilder<List<bool>>(
valueListenable: conditionsNotifier,
builder: (context, selectedConditions, _) {
return ToggleButtons(
onPressed: (int index) {
final newConditions = List<bool>.filled(3, false);
newConditions[index] = true;
conditionsNotifier.value = newConditions;
conditionNotifier.value = index == 0
? "<"
: index == 1
? "=="
: ">";
},
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: selectedConditions,
children: const [Text("<"), Text("="), Text(">")],
);
},
),
const SizedBox(height: 20),
Text(
'${value ?? 0} sec',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 20),
Slider(
value: (value ?? 0).toDouble(),
min: 0,
max: 300,
divisions: 300,
onChanged: (newValue) {
valueNotifier.value = newValue.toInt();
},
),
],
);
}, },
); );
} }
static Widget _buildOperationalValuesList( static Widget _buildValueSelector({
required BuildContext context,
required String selectedFunction,
required DeviceFunctionData? selectedFunctionData,
required List<BaseSwitchFunction> switchFunctions,
AllDevicesModel? device,
required String operationName,
}) {
if (selectedFunction == 'countdown_1' ||
selectedFunction == 'countdown_2' ||
selectedFunction == 'countdown_3') {
final initialValue = selectedFunctionData?.value ?? 200;
return _buildTemperatureSelector(
context: context,
initialValue: initialValue,
selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition,
device: device,
operationName: operationName,
selectedFunctionData: selectedFunctionData,
);
}
final selectedFn =
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
context: context,
values: values,
selectedValue: selectedFunctionData?.value,
device: device,
operationName: operationName,
selectCode: selectedFunction,
selectedFunctionData: selectedFunctionData,
);
}
static Widget _buildTemperatureSelector({
required BuildContext context,
required dynamic initialValue,
required String? currentCondition,
required String selectCode,
AllDevicesModel? device,
required String operationName,
DeviceFunctionData? selectedFunctionData,
}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_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,
selectedFunctionData, selectCode),
],
);
}
/// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle(
BuildContext context, BuildContext context,
BaseSwitchFunction function, String? currentCondition,
ValueNotifier<dynamic> valueNotifier, String selectCode,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged,
) { ) {
final values = function.getOperationalValues(); final conditions = ["<", "==", ">"];
return ValueListenableBuilder<dynamic>(
valueListenable: valueNotifier, return ToggleButtons(
builder: (context, selectedValue, _) { onPressed: (int index) {
return ListView.builder( context.read<FunctionBloc>().add(
itemCount: values.length, AddFunction(
itemBuilder: (context, index) { functionData: DeviceFunctionData(
final value = values[index]; entityId: device?.uuid ?? '',
return ListTile( functionCode: selectCode,
leading: SvgPicture.asset( operationName: operationName,
value.icon, condition: conditions[index],
width: 24, value: selectedFunctionData?.value,
height: 24, valueDescription: selectedFunctionData?.valueDescription,
), ),
title: Text(
value.description,
style: context.textTheme.bodyMedium,
),
trailing: Radio<dynamic>(
value: value.value,
groupValue: selectedValue,
onChanged: (newValue) {
valueNotifier.value = newValue;
},
), ),
); );
},
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(
'${initialValue ?? 0} sec',
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildCountDownSlider(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode,
) {
final operationalValues = SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 43200,
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(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
);
}
static Widget _buildOperationalValuesList({
required BuildContext context,
required List<SwitchOperationalValue> values,
required dynamic selectedValue,
AllDevicesModel? device,
required String operationName,
required String selectCode,
DeviceFunctionData? selectedFunctionData,
}) {
return ListView.builder(
shrinkWrap: false,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: values.length,
itemBuilder: (context, index) {
final value = values[index];
final isSelected = selectedValue == value.value;
return ListTile(
leading: SvgPicture.asset(
value.icon,
width: 24,
height: 24,
placeholderBuilder: (BuildContext context) => Container(
width: 24,
height: 24,
color: Colors.transparent,
),
),
title: Text(
value.description,
style: context.textTheme.bodyMedium,
),
trailing: Icon(
isSelected
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
size: 24,
color: isSelected
? ColorsManager.primaryColorWithOpacity
: ColorsManager.textGray,
),
onTap: () {
if (!isSelected) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);
}
}, },
); );
}, },

View File

@ -1,8 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart'; 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/routiens/bloc/functions_bloc/functions_bloc_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/models/device_functions.dart'; import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/base_switch_function.dart';
import 'package:syncrow_web/pages/routiens/models/gang_switches/two_gang_switch/two_gang_switch.dart'; import 'package:syncrow_web/pages/routiens/models/gang_switches/switch_operational_value.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@ -10,62 +14,60 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
class TwoGangSwitchHelper { class TwoGangSwitchHelper {
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog( static Future<Map<String, dynamic>?> showSwitchFunctionsDialog(
BuildContext context, List<DeviceFunction<dynamic>> functions) async { BuildContext context,
List<DeviceFunction<dynamic>> switchFunctions = functions List<DeviceFunction> functions,
.where((f) => AllDevicesModel? device,
f is TwoGangSwitch1Function || List<DeviceFunctionData>? deviceSelectedFunctions,
f is TwoGangSwitch2Function || String uniqueCustomId,
f is TwoGangCountdown1Function || ) async {
f is TwoGangCountdown2Function) List<BaseSwitchFunction> switchFunctions =
.toList(); functions.whereType<BaseSwitchFunction>().toList();
final selectedFunctionNotifier = ValueNotifier<String?>(null); return showDialog<Map<String, dynamic>?>(
final selectedValueNotifier = ValueNotifier<dynamic>(null);
final selectedConditionNotifier = ValueNotifier<String?>('<');
final selectedConditionsNotifier =
ValueNotifier<List<bool>>([true, false, false]);
await showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return ValueListenableBuilder<String?>( return BlocProvider(
valueListenable: selectedFunctionNotifier, create: (_) => FunctionBloc()
builder: (context, selectedFunction, _) { ..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
return AlertDialog( child: AlertDialog(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
content: Container( content: BlocBuilder<FunctionBloc, FunctionBlocState>(
width: selectedFunction != null ? 600 : 300, builder: (context, state) {
height: 450, final selectedFunction = state.selectedFunction;
decoration: BoxDecoration( final selectedOperationName = state.selectedOperationName;
color: Colors.white, final selectedFunctionData = state.addedFunctions
borderRadius: BorderRadius.circular(20), .firstWhere((f) => f.functionCode == selectedFunction,
), orElse: () => DeviceFunctionData(
padding: const EdgeInsets.only(top: 20), entityId: '',
child: Column( functionCode: selectedFunction ?? '',
mainAxisSize: MainAxisSize.min, operationName: '',
children: [ value: null,
const DialogHeader('2 Gangs Light Switch Condition'), ));
Expanded( return Container(
child: Row( width: selectedFunction != null ? 600 : 360,
children: [ height: 450,
// Left side: Function list decoration: BoxDecoration(
Expanded( color: Colors.white,
child: ListView.separated( borderRadius: BorderRadius.circular(20),
itemCount: switchFunctions.length, ),
separatorBuilder: (_, __) => const Divider( padding: const EdgeInsets.only(top: 20),
color: ColorsManager.dividerColor, child: Column(
), mainAxisSize: MainAxisSize.min,
itemBuilder: (context, index) { children: [
final function = switchFunctions[index]; const DialogHeader('2 Gangs Light Switch Condition'),
return ValueListenableBuilder<String?>( Expanded(
valueListenable: selectedFunctionNotifier, child: Row(
builder: (context, selectedFunction, _) { children: [
final isSelected = // Left side: Function list
selectedFunction == function.code; Expanded(
child: ListView.separated(
itemCount: switchFunctions.length,
separatorBuilder: (_, __) => const Divider(
color: ColorsManager.dividerColor,
),
itemBuilder: (context, index) {
final function = switchFunctions[index];
return ListTile( return ListTile(
tileColor: isSelected
? Colors.grey.shade100
: null,
leading: SvgPicture.asset( leading: SvgPicture.asset(
function.icon, function.icon,
width: 24, width: 24,
@ -81,191 +83,307 @@ class TwoGangSwitchHelper {
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
onTap: () { onTap: () {
selectedFunctionNotifier.value = context
function.code; .read<FunctionBloc>()
selectedValueNotifier.value = function .add(SelectFunction(
is TwoGangCountdown1Function || functionCode: function.code,
function operationName:
is TwoGangCountdown2Function function.operationName,
? 0 ));
: null;
}, },
); );
}, },
); ),
},
),
),
// Right side: Value selector
if (selectedFunction != null)
Expanded(
child: ValueListenableBuilder<dynamic>(
valueListenable: selectedValueNotifier,
builder: (context, selectedValue, _) {
final selectedFn = switchFunctions.firstWhere(
(f) => f.code == selectedFunction,
);
if (selectedFn is TwoGangCountdown1Function ||
selectedFn is TwoGangCountdown2Function) {
return _buildCountDownSelector(
context,
selectedValueNotifier,
selectedConditionNotifier,
selectedConditionsNotifier,
);
}
return _buildOperationalValuesList(
context,
selectedFn as BaseSwitchFunction,
selectedValueNotifier,
);
},
), ),
), // Right side: Value selector
], if (selectedFunction != null)
), Expanded(
child: _buildValueSelector(
context: context,
selectedFunction: selectedFunction,
selectedFunctionData: selectedFunctionData,
switchFunctions: switchFunctions,
device: device,
operationName: selectedOperationName ?? '',
),
),
],
),
),
Container(
height: 1,
width: double.infinity,
color: ColorsManager.greyColor,
),
DialogFooter(
onCancel: () {
Navigator.pop(context);
},
onConfirm: state.addedFunctions.isNotEmpty
? () {
/// add the functions to the routine bloc
// for (var function in state.addedFunctions) {
// context.read<RoutineBloc>().add(
// AddFunctionToRoutine(
// function,
// uniqueCustomId
// ),
// );
// }
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
state.addedFunctions,
uniqueCustomId,
),
);
// Return the device data to be added to the container
Navigator.pop(context, {
'deviceId': functions.first.deviceId,
});
}
: null,
isConfirmEnabled: selectedFunction != null,
),
],
), ),
DialogFooter( );
onCancel: () => Navigator.pop(context),
onConfirm: selectedFunction != null &&
selectedValueNotifier.value != null
? () {
final selectedFn = switchFunctions.firstWhere(
(f) => f.code == selectedFunction,
);
final value = selectedValueNotifier.value;
final functionData = DeviceFunctionData(
entityId: selectedFn.deviceId,
functionCode: selectedFn.code,
operationName: selectedFn.operationName,
value: value,
condition: selectedConditionNotifier.value,
valueDescription: selectedFn
is TwoGangCountdown1Function ||
selectedFn is TwoGangCountdown2Function
? '${value} sec'
: ((selectedFn as BaseSwitchFunction)
.getOperationalValues()
.firstWhere((v) => v.value == value)
.description),
);
Navigator.pop(
context, {selectedFn.code: functionData});
}
: null,
isConfirmEnabled: selectedFunction != null,
),
],
),
),
);
},
);
},
).then((value) {
selectedFunctionNotifier.dispose();
selectedValueNotifier.dispose();
selectedConditionNotifier.dispose();
selectedConditionsNotifier.dispose();
return value;
});
}
static Widget _buildOperationalValuesList(
BuildContext context,
BaseSwitchFunction function,
ValueNotifier<dynamic> valueNotifier,
) {
final values = function.getOperationalValues();
return ValueListenableBuilder<dynamic>(
valueListenable: valueNotifier,
builder: (context, selectedValue, _) {
return ListView.builder(
itemCount: values.length,
itemBuilder: (context, index) {
final value = values[index];
return ListTile(
leading: SvgPicture.asset(
value.icon,
width: 24,
height: 24,
),
title: Text(
value.description,
style: context.textTheme.bodyMedium,
),
trailing: Radio<dynamic>(
value: value.value,
groupValue: selectedValue,
onChanged: (newValue) {
valueNotifier.value = newValue;
}, },
), ),
); ));
},
);
}, },
); );
} }
static Widget _buildCountDownSelector( static Widget _buildValueSelector({
required BuildContext context,
required String selectedFunction,
required DeviceFunctionData? selectedFunctionData,
required List<BaseSwitchFunction> switchFunctions,
AllDevicesModel? device,
required String operationName,
}) {
if (selectedFunction == 'countdown_1' ||
selectedFunction == 'countdown_2') {
final initialValue = selectedFunctionData?.value ?? 200;
return _buildTemperatureSelector(
context: context,
initialValue: initialValue,
selectCode: selectedFunction,
currentCondition: selectedFunctionData?.condition,
device: device,
operationName: operationName,
selectedFunctionData: selectedFunctionData,
);
}
final selectedFn =
switchFunctions.firstWhere((f) => f.code == selectedFunction);
final values = selectedFn.getOperationalValues();
return _buildOperationalValuesList(
context: context,
values: values,
selectedValue: selectedFunctionData?.value,
device: device,
operationName: operationName,
selectCode: selectedFunction,
selectedFunctionData: selectedFunctionData,
);
}
static Widget _buildTemperatureSelector({
required BuildContext context,
required dynamic initialValue,
required String? currentCondition,
required String selectCode,
AllDevicesModel? device,
required String operationName,
DeviceFunctionData? selectedFunctionData,
}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_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,
selectedFunctionData, selectCode),
],
);
}
/// Build condition toggle for AC functions dialog
static Widget _buildConditionToggle(
BuildContext context, BuildContext context,
ValueNotifier<dynamic> valueNotifier, String? currentCondition,
ValueNotifier<String?> conditionNotifier, String selectCode,
ValueNotifier<List<bool>> conditionsNotifier, AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
// Function(String) onConditionChanged,
) { ) {
return ValueListenableBuilder<dynamic>( final conditions = ["<", "==", ">"];
valueListenable: valueNotifier,
builder: (context, value, _) { return ToggleButtons(
return Column( onPressed: (int index) {
mainAxisAlignment: MainAxisAlignment.center, context.read<FunctionBloc>().add(
children: [ AddFunction(
ValueListenableBuilder<List<bool>>( functionData: DeviceFunctionData(
valueListenable: conditionsNotifier, entityId: device?.uuid ?? '',
builder: (context, selectedConditions, _) { functionCode: selectCode,
return ToggleButtons( operationName: operationName,
onPressed: (int index) { condition: conditions[index],
final newConditions = List<bool>.filled(3, false); value: selectedFunctionData?.value,
newConditions[index] = true; valueDescription: selectedFunctionData?.valueDescription,
conditionsNotifier.value = newConditions; ),
conditionNotifier.value = index == 0 ),
? "<" );
: index == 1 },
? "==" borderRadius: const BorderRadius.all(Radius.circular(8)),
: ">"; selectedBorderColor: ColorsManager.primaryColorWithOpacity,
}, selectedColor: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(8)), fillColor: ColorsManager.primaryColorWithOpacity,
selectedBorderColor: ColorsManager.primaryColorWithOpacity, color: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white, constraints: const BoxConstraints(
fillColor: ColorsManager.primaryColorWithOpacity, minHeight: 40.0,
color: ColorsManager.primaryColorWithOpacity, minWidth: 40.0,
constraints: const BoxConstraints( ),
minHeight: 40.0, isSelected:
minWidth: 40.0, conditions.map((c) => c == (currentCondition ?? "==")).toList(),
), children: conditions.map((c) => Text(c)).toList(),
isSelected: selectedConditions, );
children: const [Text("<"), Text("="), Text(">")], }
);
}, /// 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(
'${initialValue ?? 0} sec',
style: context.textTheme.headlineMedium!.copyWith(
color: ColorsManager.primaryColorWithOpacity,
),
),
);
}
static Widget _buildCountDownSlider(
BuildContext context,
dynamic initialValue,
AllDevicesModel? device,
String operationName,
DeviceFunctionData? selectedFunctionData,
String selectCode,
) {
final operationalValues = SwitchOperationalValue(
icon: '',
description: "sec",
value: 0.0,
minValue: 0,
maxValue: 43200,
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(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value,
condition: selectedFunctionData?.condition,
valueDescription: selectedFunctionData?.valueDescription,
),
),
);
},
);
}
static Widget _buildOperationalValuesList({
required BuildContext context,
required List<SwitchOperationalValue> values,
required dynamic selectedValue,
AllDevicesModel? device,
required String operationName,
required String selectCode,
DeviceFunctionData? selectedFunctionData,
}) {
return ListView.builder(
shrinkWrap: false,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: values.length,
itemBuilder: (context, index) {
final value = values[index];
final isSelected = selectedValue == value.value;
return ListTile(
leading: SvgPicture.asset(
value.icon,
width: 24,
height: 24,
placeholderBuilder: (BuildContext context) => Container(
width: 24,
height: 24,
color: Colors.transparent,
), ),
const SizedBox(height: 20), ),
Text( title: Text(
'${value ?? 0} sec', value.description,
style: Theme.of(context).textTheme.headlineMedium, style: context.textTheme.bodyMedium,
), ),
const SizedBox(height: 20), trailing: Icon(
Slider( isSelected
value: (value ?? 0).toDouble(), ? Icons.radio_button_checked
min: 0, : Icons.radio_button_unchecked,
max: 300, size: 24,
divisions: 300, color: isSelected
onChanged: (newValue) { ? ColorsManager.primaryColorWithOpacity
valueNotifier.value = newValue.toInt(); : ColorsManager.textGray,
}, ),
), onTap: () {
], if (!isSelected) {
context.read<FunctionBloc>().add(
AddFunction(
functionData: DeviceFunctionData(
entityId: device?.uuid ?? '',
functionCode: selectCode,
operationName: operationName,
value: value.value,
condition: selectedFunctionData?.condition,
valueDescription:
selectedFunctionData?.valueDescription,
),
),
);
}
},
); );
}, },
); );

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
abstract class DeviceFunction<T> { abstract class DeviceFunction<T> {
final String deviceId; final String deviceId;
final String deviceName; final String deviceName;
@ -24,7 +22,6 @@ class DeviceFunctionData {
final dynamic value; final dynamic value;
final String? condition; final String? condition;
final String? valueDescription; final String? valueDescription;
final UniqueKey uniqueKey;
DeviceFunctionData({ DeviceFunctionData({
required this.entityId, required this.entityId,
@ -34,7 +31,7 @@ class DeviceFunctionData {
required this.value, required this.value,
this.condition, this.condition,
this.valueDescription, this.valueDescription,
}) : uniqueKey = UniqueKey(); });
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {

View File

@ -1,29 +1,30 @@
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';
class RoutineItem { // class RoutineItem {
final AllDevicesModel device; // final AllDevicesModel device;
final String? function; // final String? function;
final dynamic value; // final dynamic value;
RoutineItem({ // RoutineItem({
required this.device, // required this.device,
this.function, // this.function,
this.value, // this.value,
}); // });
Map<String, dynamic> toMap() { // Map<String, dynamic> toMap() {
return { // return {
'device': device, // 'device': device,
'function': function, // 'function': function,
'value': value, // 'value': value,
}; // };
} // }
factory RoutineItem.fromMap(Map<String, dynamic> map) { // factory RoutineItem.fromMap(Map<String, dynamic> map) {
return RoutineItem( // return RoutineItem(
device: map['device'] as AllDevicesModel, // device: map['device'] as AllDevicesModel,
function: map['function'], // function: map['function'],
value: map['value'], // value: map['value'],
); // );
} // }
} // }
// : uniqueCustomId = uniqueCustomId ?? const Uuid().v4()

View File

@ -1,5 +1,3 @@
import 'dart:convert';
class ScenesModel { class ScenesModel {
final String id; final String id;
final String name; final String name;
@ -7,11 +5,13 @@ class ScenesModel {
final String type; final String type;
final String? icon; final String? icon;
ScenesModel({required this.id, required this.name, required this.status, required this.type, this.icon}); ScenesModel({
required this.id,
factory ScenesModel.fromRawJson(String str) => ScenesModel.fromJson(json.decode(str)); required this.name,
required this.status,
String toRawJson() => json.encode(toJson()); required this.type,
this.icon,
});
factory ScenesModel.fromJson(Map<String, dynamic> json) => ScenesModel( factory ScenesModel.fromJson(Map<String, dynamic> json) => ScenesModel(
id: json["id"], id: json["id"],

View File

@ -22,9 +22,8 @@ class DraggableCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>( return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) { builder: (context, state) {
final deviceFunctions = state.selectedFunctions final deviceFunctions =
.where((f) => f.entityId == deviceData['deviceId']) state.selectedFunctions[deviceData['uniqueCustomId']] ?? [];
.toList();
return Draggable<Map<String, dynamic>>( return Draggable<Map<String, dynamic>>(
data: deviceData, data: deviceData,
@ -45,7 +44,7 @@ class DraggableCard extends StatelessWidget {
color: ColorsManager.whiteColors, color: ColorsManager.whiteColors,
child: Container( child: Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
width: 90, width: 110,
height: deviceFunctions.isEmpty ? 123 : null, height: deviceFunctions.isEmpty ? 123 : null,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

View File

@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
import 'package:uuid/uuid.dart';
class RoutineDevices extends StatelessWidget { class RoutineDevices extends StatelessWidget {
const RoutineDevices({super.key}); const RoutineDevices({super.key});

View File

@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart'; import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart';
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart'; import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
import 'package:uuid/uuid.dart';
class ThenContainer extends StatelessWidget { class ThenContainer extends StatelessWidget {
const ThenContainer({super.key}); const ThenContainer({super.key});
@ -15,34 +16,38 @@ class ThenContainer extends StatelessWidget {
builder: (context, state) { builder: (context, state) {
return DragTarget<Map<String, dynamic>>( return DragTarget<Map<String, dynamic>>(
builder: (context, candidateData, rejectedData) { builder: (context, candidateData, rejectedData) {
return Container( return SingleChildScrollView(
padding: const EdgeInsets.all(16), child: Container(
width: double.infinity, padding: const EdgeInsets.all(16),
child: Column( width: double.infinity,
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
const Text('THEN', children: [
style: const Text('THEN',
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), style: TextStyle(
const SizedBox(height: 16), fontSize: 18, fontWeight: FontWeight.bold)),
Wrap( const SizedBox(height: 16),
spacing: 8, Wrap(
runSpacing: 8, spacing: 8,
children: state.thenItems runSpacing: 8,
.map((item) => DraggableCard( children: state.thenItems
// key: Key(item['key']!), .map((item) => DraggableCard(
imagePath: item['imagePath']!, // key: Key(item['key']!),
title: item['title']!, imagePath: item['imagePath']!,
deviceData: item, title: item['title']!,
)) deviceData: item,
.toList(), ))
), .toList(),
], ),
],
),
), ),
); );
}, },
onWillAccept: (data) => data != null, onWillAccept: (data) => data != null,
onAccept: (data) async { onAccept: (data) async {
final uniqueCustomId = const Uuid().v4();
data['uniqueCustomId'] = uniqueCustomId;
final result = final result =
await DeviceDialogHelper.showDeviceDialog(context, data); await DeviceDialogHelper.showDeviceDialog(context, data);
// if (result != null) { // if (result != null) {

View File

@ -57,6 +57,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.18.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
url: "https://pub.dev"
source: hosted
version: "3.0.6"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -137,6 +145,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.0.0"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
fl_chart: fl_chart:
dependency: "direct main" dependency: "direct main"
description: description:
@ -533,6 +549,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.10.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -589,6 +613,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.2" version: "1.3.2"
uuid:
dependency: "direct main"
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.5.1"
vector_graphics: vector_graphics:
dependency: transitive dependency: transitive
description: description:

View File

@ -51,6 +51,7 @@ dependencies:
flutter_dotenv: ^5.1.0 flutter_dotenv: ^5.1.0
fl_chart: ^0.69.0 fl_chart: ^0.69.0
time_picker_spinner: ^1.0.0 time_picker_spinner: ^1.0.0
uuid: ^4.4.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: