mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 07:07:19 +00:00
Implemented side tree to devices and rountines screen
This commit is contained in:
411
lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart
Normal file
411
lib/pages/routines/widgets/routine_dialogs/ac_dialog.dart
Normal file
@ -0,0 +1,411 @@
|
||||
import 'package:flutter/material.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/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_operational_value.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.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/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/functions_bloc/functions_bloc_bloc.dart';
|
||||
|
||||
class ACHelper {
|
||||
static Future<Map<String, dynamic>?> showACFunctionsDialog(
|
||||
BuildContext context,
|
||||
List<DeviceFunction> functions,
|
||||
AllDevicesModel? device,
|
||||
List<DeviceFunctionData>? deviceSelectedFunctions,
|
||||
String uniqueCustomId,
|
||||
bool? removeComparetors,
|
||||
) async {
|
||||
List<ACFunction> acFunctions = functions.whereType<ACFunction>().toList();
|
||||
|
||||
return showDialog<Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
|
||||
child: AlertDialog(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
|
||||
builder: (context, state) {
|
||||
final selectedFunction = state.selectedFunction;
|
||||
final selectedOperationName = state.selectedOperationName;
|
||||
final selectedFunctionData =
|
||||
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
|
||||
orElse: () => DeviceFunctionData(
|
||||
entityId: '',
|
||||
functionCode: selectedFunction ?? '',
|
||||
operationName: '',
|
||||
value: null,
|
||||
));
|
||||
|
||||
return Container(
|
||||
width: selectedFunction != null ? 600 : 360,
|
||||
height: 450,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('AC Functions'),
|
||||
Expanded(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Function list
|
||||
SizedBox(
|
||||
width: selectedFunction != null ? 320 : 360,
|
||||
child: _buildFunctionsList(
|
||||
context: context,
|
||||
acFunctions: acFunctions,
|
||||
onFunctionSelected: (functionCode, operationName) =>
|
||||
context.read<FunctionBloc>().add(SelectFunction(
|
||||
functionCode: functionCode,
|
||||
operationName: operationName,
|
||||
)),
|
||||
),
|
||||
),
|
||||
// Value selector
|
||||
if (selectedFunction != null)
|
||||
Expanded(
|
||||
child: _buildValueSelector(
|
||||
context: context,
|
||||
selectedFunction: selectedFunction,
|
||||
selectedFunctionData: selectedFunctionData,
|
||||
acFunctions: acFunctions,
|
||||
device: device,
|
||||
operationName: selectedOperationName ?? '',
|
||||
removeComparators: removeComparetors,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DialogFooter(
|
||||
onCancel: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
onConfirm: state.addedFunctions.isNotEmpty
|
||||
? () {
|
||||
/// add the functions to the routine bloc
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
).then((value) {
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
/// Build functions list for AC functions dialog
|
||||
static Widget _buildFunctionsList({
|
||||
required BuildContext context,
|
||||
required List<ACFunction> acFunctions,
|
||||
required Function(String, String) onFunctionSelected,
|
||||
}) {
|
||||
return ListView.separated(
|
||||
shrinkWrap: false,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount: acFunctions.length,
|
||||
separatorBuilder: (context, index) => const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 40.0),
|
||||
child: Divider(
|
||||
color: ColorsManager.dividerColor,
|
||||
),
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final function = acFunctions[index];
|
||||
return ListTile(
|
||||
leading: SvgPicture.asset(
|
||||
function.icon,
|
||||
width: 24,
|
||||
height: 24,
|
||||
placeholderBuilder: (BuildContext context) => Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
function.operationName,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
trailing: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () => onFunctionSelected(
|
||||
function.code,
|
||||
function.operationName,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Build value selector for AC functions dialog
|
||||
static Widget _buildValueSelector({
|
||||
required BuildContext context,
|
||||
required String selectedFunction,
|
||||
required DeviceFunctionData? selectedFunctionData,
|
||||
required List<ACFunction> acFunctions,
|
||||
AllDevicesModel? device,
|
||||
required String operationName,
|
||||
bool? removeComparators,
|
||||
}) {
|
||||
if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') {
|
||||
final initialValue = selectedFunctionData?.value ?? 250;
|
||||
return _buildTemperatureSelector(
|
||||
context: context,
|
||||
initialValue: initialValue,
|
||||
selectCode: selectedFunction,
|
||||
currentCondition: selectedFunctionData?.condition,
|
||||
device: device,
|
||||
operationName: operationName,
|
||||
selectedFunctionData: selectedFunctionData,
|
||||
removeComparators: removeComparators,
|
||||
);
|
||||
}
|
||||
|
||||
final selectedFn = acFunctions.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,
|
||||
);
|
||||
}
|
||||
|
||||
/// Build temperature selector for AC functions dialog
|
||||
static Widget _buildTemperatureSelector({
|
||||
required BuildContext context,
|
||||
required dynamic initialValue,
|
||||
required String? currentCondition,
|
||||
required String selectCode,
|
||||
AllDevicesModel? device,
|
||||
required String operationName,
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
bool? removeComparators,
|
||||
}) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (removeComparators != true)
|
||||
_buildConditionToggle(
|
||||
context,
|
||||
currentCondition,
|
||||
selectCode,
|
||||
device,
|
||||
operationName,
|
||||
selectedFunctionData,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildTemperatureDisplay(
|
||||
context,
|
||||
initialValue,
|
||||
device,
|
||||
operationName,
|
||||
selectedFunctionData,
|
||||
selectCode,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildTemperatureSlider(
|
||||
context,
|
||||
initialValue,
|
||||
device,
|
||||
operationName,
|
||||
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,
|
||||
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 _buildTemperatureDisplay(
|
||||
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 ?? 200) / 10}°C',
|
||||
style: context.textTheme.headlineMedium!.copyWith(
|
||||
color: ColorsManager.primaryColorWithOpacity,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildTemperatureSlider(
|
||||
BuildContext context,
|
||||
dynamic initialValue,
|
||||
AllDevicesModel? device,
|
||||
String operationName,
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
String selectCode,
|
||||
) {
|
||||
return Slider(
|
||||
value: initialValue is int ? initialValue.toDouble() : 200.0,
|
||||
min: 200,
|
||||
max: 300,
|
||||
divisions: 10,
|
||||
label: '${((initialValue ?? 160) / 10).toInt()}°C',
|
||||
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<ACOperationalValue> values,
|
||||
required dynamic selectedValue,
|
||||
AllDevicesModel? device,
|
||||
required String operationName,
|
||||
required String selectCode,
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
|
||||
// required Function(dynamic) onValueChanged,
|
||||
}) {
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class AutomationDialog extends StatefulWidget {
|
||||
final String automationName;
|
||||
final String automationId;
|
||||
final String uniqueCustomId;
|
||||
final String? passedAutomationActionExecutor;
|
||||
|
||||
const AutomationDialog({
|
||||
super.key,
|
||||
required this.automationName,
|
||||
required this.automationId,
|
||||
required this.uniqueCustomId,
|
||||
this.passedAutomationActionExecutor,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AutomationDialog> createState() => _AutomationDialogState();
|
||||
}
|
||||
|
||||
class _AutomationDialogState extends State<AutomationDialog> {
|
||||
String? selectedAutomationActionExecutor;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
List<DeviceFunctionData>? functions =
|
||||
context.read<RoutineBloc>().state.selectedFunctions[widget.uniqueCustomId];
|
||||
for (DeviceFunctionData data in functions ?? []) {
|
||||
if (data.entityId == widget.automationId) {
|
||||
selectedAutomationActionExecutor = data.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Container(
|
||||
width: 400,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
DialogHeader(widget.automationName),
|
||||
const SizedBox(height: 16),
|
||||
ListTile(
|
||||
leading: SvgPicture.asset(Assets.acPower, width: 24, height: 24),
|
||||
title: const Text('Enable'),
|
||||
trailing: Radio<String?>(
|
||||
value: 'rule_enable',
|
||||
groupValue: selectedAutomationActionExecutor,
|
||||
onChanged: (String? value) {
|
||||
setState(() {
|
||||
selectedAutomationActionExecutor = 'rule_enable';
|
||||
});
|
||||
}),
|
||||
),
|
||||
ListTile(
|
||||
leading: SvgPicture.asset(Assets.acPowerOff, width: 24, height: 24),
|
||||
title: const Text('Disable'),
|
||||
trailing: Radio<String?>(
|
||||
value: 'rule_disable',
|
||||
groupValue: selectedAutomationActionExecutor,
|
||||
onChanged: (String? value) {
|
||||
setState(() {
|
||||
selectedAutomationActionExecutor = 'rule_disable';
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DialogFooter(
|
||||
onConfirm: () {
|
||||
if (selectedAutomationActionExecutor != null) {
|
||||
context.read<RoutineBloc>().add(
|
||||
AddFunctionToRoutine(
|
||||
[
|
||||
DeviceFunctionData(
|
||||
entityId: widget.automationId,
|
||||
functionCode: 'automation',
|
||||
value: selectedAutomationActionExecutor,
|
||||
operationName: 'Automation',
|
||||
),
|
||||
],
|
||||
widget.uniqueCustomId,
|
||||
),
|
||||
);
|
||||
}
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
onCancel: () => Navigator.of(context).pop(),
|
||||
isConfirmEnabled: selectedAutomationActionExecutor != null,
|
||||
dialogWidth: 400,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
85
lib/pages/routines/widgets/routine_dialogs/delay_dialog.dart
Normal file
85
lib/pages/routines/widgets/routine_dialogs/delay_dialog.dart
Normal file
@ -0,0 +1,85 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_footer.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
|
||||
|
||||
class DelayHelper {
|
||||
static Future<Map<String, dynamic>?> showDelayPickerDialog(
|
||||
BuildContext context, Map<String, dynamic> data) async {
|
||||
int hours = 0;
|
||||
int minutes = 0;
|
||||
|
||||
return showDialog<Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
final routineBloc = context.read<RoutineBloc>();
|
||||
int totalSec = 0;
|
||||
|
||||
final selectedFunctionData =
|
||||
routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? [];
|
||||
|
||||
if (selectedFunctionData.isNotEmpty) {
|
||||
totalSec = selectedFunctionData[0].value;
|
||||
// Convert seconds to hours and minutes
|
||||
hours = totalSec ~/ 3600;
|
||||
minutes = (totalSec % 3600) ~/ 60;
|
||||
}
|
||||
return AlertDialog(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: Container(
|
||||
width: 600,
|
||||
height: 300,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('Select Delay Duration'),
|
||||
Expanded(
|
||||
child: CupertinoTimerPicker(
|
||||
mode: CupertinoTimerPickerMode.hm,
|
||||
initialTimerDuration: Duration(hours: hours, minutes: minutes),
|
||||
onTimerDurationChanged: (Duration newDuration) {
|
||||
hours = newDuration.inHours;
|
||||
minutes = newDuration.inMinutes % 60;
|
||||
},
|
||||
),
|
||||
),
|
||||
DialogFooter(
|
||||
onCancel: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
onConfirm: () {
|
||||
int totalSeconds = (hours * 3600) + (minutes * 60);
|
||||
context.read<RoutineBloc>().add(AddFunctionToRoutine(
|
||||
[
|
||||
DeviceFunctionData(
|
||||
entityId: 'delay',
|
||||
functionCode: 'delay',
|
||||
operationName: 'Delay',
|
||||
value: totalSeconds,
|
||||
)
|
||||
],
|
||||
data['uniqueCustomId'],
|
||||
));
|
||||
|
||||
Navigator.pop(context, {
|
||||
'deviceId': 'delay',
|
||||
'value': totalSeconds,
|
||||
});
|
||||
},
|
||||
isConfirmEnabled: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class DiscardDialog {
|
||||
static void show(BuildContext context) {
|
||||
context.customAlertDialog(
|
||||
alertBody: Container(
|
||||
height: 150,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'If you close, you will lose all the changes you have made.',
|
||||
textAlign: TextAlign.center,
|
||||
style: context.textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.red,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Text(
|
||||
'Are you sure you wish to close?',
|
||||
style: context.textTheme.bodyMedium!.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
)
|
||||
],
|
||||
)),
|
||||
title: 'Discard',
|
||||
titleStyle: context.textTheme.titleLarge!.copyWith(
|
||||
color: ColorsManager.red,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
onDismissText: "Don’t Close",
|
||||
onConfirmText: "Close",
|
||||
onDismissColor: ColorsManager.grayColor,
|
||||
onConfirmColor: ColorsManager.red.withOpacity(0.8),
|
||||
onDismiss: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
onConfirm: () {
|
||||
context.read<RoutineBloc>().add(ResetRoutineState());
|
||||
Navigator.pop(context);
|
||||
BlocProvider.of<RoutineBloc>(context).add(
|
||||
const CreateNewRoutineViewEvent(createRoutineView: false),
|
||||
);
|
||||
BlocProvider.of<RoutineBloc>(context).add(
|
||||
const TriggerSwitchTabsEvent(isRoutineTab: true),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/app_enum.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:time_picker_spinner/time_picker_spinner.dart';
|
||||
|
||||
class EffectPeriodHelper {
|
||||
static Future<List<String>?> showCustomTimePicker(BuildContext context) async {
|
||||
String selectedStartTime = "00:00";
|
||||
String selectedEndTime = "23:59";
|
||||
PageController pageController = PageController(initialPage: 0);
|
||||
|
||||
DateTime startDateTime = DateTime(2022, 1, 1, 0, 0);
|
||||
DateTime endDateTime = DateTime(2022, 1, 1, 23, 59);
|
||||
|
||||
context.customAlertDialog(
|
||||
alertBody: SizedBox(
|
||||
height: 250,
|
||||
child: PageView(
|
||||
controller: pageController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
_buildTimePickerPage(
|
||||
context: context,
|
||||
pageController: pageController,
|
||||
isStartTime: true,
|
||||
time: startDateTime,
|
||||
onTimeChange: (time) {
|
||||
selectedStartTime =
|
||||
"${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}";
|
||||
},
|
||||
),
|
||||
_buildTimePickerPage(
|
||||
context: context,
|
||||
pageController: pageController,
|
||||
isStartTime: false,
|
||||
time: endDateTime,
|
||||
onTimeChange: (time) {
|
||||
selectedEndTime =
|
||||
"${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}";
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
title: "Custom",
|
||||
onConfirm: () {
|
||||
context.read<EffectPeriodBloc>().add(
|
||||
SetCustomTime(selectedStartTime, selectedEndTime),
|
||||
);
|
||||
context.read<EffectPeriodBloc>().add(
|
||||
const SetPeriod(EnumEffectivePeriodOptions.custom),
|
||||
);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
static Widget _buildTimePickerPage({
|
||||
required BuildContext context,
|
||||
required PageController pageController,
|
||||
required bool isStartTime,
|
||||
required DateTime time,
|
||||
required Function(DateTime) onTimeChange,
|
||||
}) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (!isStartTime)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
pageController.previousPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
},
|
||||
child: const Text('Start'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
child: Text(isStartTime ? "Start" : "End",
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.textPrimaryColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 10)),
|
||||
),
|
||||
if (isStartTime)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
},
|
||||
child: Text('End',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.textPrimaryColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 10)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TimePickerSpinner(
|
||||
is24HourMode: false,
|
||||
normalTextStyle: const TextStyle(
|
||||
fontSize: 24,
|
||||
color: Colors.grey,
|
||||
),
|
||||
highlightedTextStyle: const TextStyle(
|
||||
fontSize: 24,
|
||||
color: ColorsManager.primaryColor,
|
||||
),
|
||||
spacing: 20,
|
||||
itemHeight: 50,
|
||||
isForce2Digits: true,
|
||||
time: time,
|
||||
onTimeChange: onTimeChange,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
static String formatEnumValue(EnumEffectivePeriodOptions value) {
|
||||
switch (value) {
|
||||
case EnumEffectivePeriodOptions.allDay:
|
||||
return "All Day";
|
||||
case EnumEffectivePeriodOptions.daytime:
|
||||
return "Daytime";
|
||||
case EnumEffectivePeriodOptions.night:
|
||||
return "Night";
|
||||
case EnumEffectivePeriodOptions.custom:
|
||||
return "Custom";
|
||||
case EnumEffectivePeriodOptions.none:
|
||||
return "None";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,384 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/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/helper/duration_format_helper.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/switch_operational_value.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/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class OneGangSwitchHelper {
|
||||
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog(
|
||||
BuildContext context,
|
||||
List<DeviceFunction> functions,
|
||||
AllDevicesModel? device,
|
||||
List<DeviceFunctionData>? deviceSelectedFunctions,
|
||||
String uniqueCustomId,
|
||||
bool removeComparetors,
|
||||
) async {
|
||||
List<BaseSwitchFunction> acFunctions = functions.whereType<BaseSwitchFunction>().toList();
|
||||
|
||||
return showDialog<Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
|
||||
child: AlertDialog(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
|
||||
builder: (context, state) {
|
||||
final selectedFunction = state.selectedFunction;
|
||||
final selectedOperationName = state.selectedOperationName;
|
||||
final selectedFunctionData =
|
||||
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
|
||||
orElse: () => DeviceFunctionData(
|
||||
entityId: '',
|
||||
functionCode: selectedFunction ?? '',
|
||||
operationName: '',
|
||||
value: null,
|
||||
));
|
||||
return Container(
|
||||
width: selectedFunction != null ? 600 : 360,
|
||||
height: 450,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('1 Gang Light Switch Condition'),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
// Left side: Function list
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
itemCount: acFunctions.length,
|
||||
separatorBuilder: (_, __) => const Divider(
|
||||
color: ColorsManager.dividerColor,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final function = acFunctions[index];
|
||||
return ListTile(
|
||||
leading: SvgPicture.asset(
|
||||
function.icon,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
title: Text(
|
||||
function.operationName,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
trailing: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
context.read<FunctionBloc>().add(SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName: function.operationName,
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// Right side: Value selector
|
||||
if (selectedFunction != null)
|
||||
Expanded(
|
||||
child: _buildValueSelector(
|
||||
context: context,
|
||||
selectedFunction: selectedFunction,
|
||||
selectedFunctionData: selectedFunctionData,
|
||||
acFunctions: acFunctions,
|
||||
device: device,
|
||||
operationName: selectedOperationName ?? '',
|
||||
removeComparetors: removeComparetors,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildValueSelector({
|
||||
required BuildContext context,
|
||||
required String selectedFunction,
|
||||
required DeviceFunctionData? selectedFunctionData,
|
||||
required List<BaseSwitchFunction> acFunctions,
|
||||
AllDevicesModel? device,
|
||||
required String operationName,
|
||||
required bool removeComparetors,
|
||||
}) {
|
||||
if (selectedFunction == 'countdown_1') {
|
||||
final initialValue = selectedFunctionData?.value ?? 200;
|
||||
return _buildCountDownSelector(
|
||||
context: context,
|
||||
initialValue: initialValue,
|
||||
selectCode: selectedFunction,
|
||||
currentCondition: selectedFunctionData?.condition,
|
||||
device: device,
|
||||
operationName: operationName,
|
||||
selectedFunctionData: selectedFunctionData,
|
||||
removeComparetors: removeComparetors,
|
||||
);
|
||||
}
|
||||
|
||||
final selectedFn = acFunctions.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 _buildCountDownSelector({
|
||||
required BuildContext context,
|
||||
required dynamic initialValue,
|
||||
required String? currentCondition,
|
||||
required String selectCode,
|
||||
AllDevicesModel? device,
|
||||
required String operationName,
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
required bool removeComparetors,
|
||||
}) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
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, 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,
|
||||
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(
|
||||
BuildContext context,
|
||||
dynamic initialValue,
|
||||
AllDevicesModel? device,
|
||||
String operationName,
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
String selectCode,
|
||||
) {
|
||||
final operationalValues = SwitchOperationalValue(
|
||||
icon: '',
|
||||
description: "sec",
|
||||
value: 0.0,
|
||||
minValue: 0,
|
||||
maxValue: 86400,
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
431
lib/pages/routines/widgets/routine_dialogs/setting_dialog.dart
Normal file
431
lib/pages/routines/widgets/routine_dialogs/setting_dialog.dart
Normal file
@ -0,0 +1,431 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_event.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/effective_period/effect_period_state.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/routine_bloc/routine_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_bloc.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_event.dart';
|
||||
import 'package:syncrow_web/pages/routines/bloc/setting_bloc/setting_state.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
|
||||
import 'package:syncrow_web/pages/routines/models/icon_model.dart';
|
||||
import 'package:syncrow_web/pages/routines/view/effective_period_view.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/delete_scene.dart';
|
||||
import 'package:syncrow_web/pages/routines/widgets/dialog_header.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class SettingHelper {
|
||||
static Future<String?> showSettingDialog({
|
||||
required BuildContext context,
|
||||
String? iconId,
|
||||
}) async {
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
final isAutomation = context.read<RoutineBloc>().state.isAutomation;
|
||||
final effectiveTime = context.read<RoutineBloc>().state.effectiveTime;
|
||||
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
if (effectiveTime != null)
|
||||
BlocProvider(
|
||||
create: (_) => EffectPeriodBloc()..add(InitialEffectPeriodEvent(effectiveTime)),
|
||||
),
|
||||
if (effectiveTime == null)
|
||||
BlocProvider(
|
||||
create: (_) => EffectPeriodBloc(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (_) => SettingBloc()..add(InitialEvent(selectedIcon: iconId ?? ''))),
|
||||
],
|
||||
child: AlertDialog(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: BlocBuilder<EffectPeriodBloc, EffectPeriodState>(
|
||||
builder: (context, effectPeriodState) {
|
||||
return BlocBuilder<SettingBloc, SettingState>(
|
||||
builder: (context, settingState) {
|
||||
String selectedIcon = '';
|
||||
List<IconModel> list = [];
|
||||
if (settingState is TabToRunSettingLoaded) {
|
||||
selectedIcon = settingState.selectedIcon;
|
||||
list = settingState.iconList;
|
||||
}
|
||||
return Container(
|
||||
width: context.read<SettingBloc>().isExpanded ? 800 : 400,
|
||||
height: context.read<SettingBloc>().isExpanded && isAutomation ? 500 : 350,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const DialogHeader('Settings'),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 400,
|
||||
child: isAutomation
|
||||
? Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10, left: 10, right: 10, bottom: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {},
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Validity',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color:
|
||||
ColorsManager.textPrimaryColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14),
|
||||
),
|
||||
const Icon(
|
||||
Icons.arrow_forward_ios_outlined,
|
||||
color: ColorsManager.textGray,
|
||||
size: 15,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
const Divider(
|
||||
color: ColorsManager.graysColor,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
InkWell(
|
||||
onTap: () {
|
||||
BlocProvider.of<SettingBloc>(context).add(
|
||||
FetchIcons(
|
||||
expanded: !context
|
||||
.read<SettingBloc>()
|
||||
.isExpanded));
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Effective Period',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color:
|
||||
ColorsManager.textPrimaryColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14),
|
||||
),
|
||||
const Icon(
|
||||
Icons.arrow_forward_ios_outlined,
|
||||
color: ColorsManager.textGray,
|
||||
size: 15,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
const Divider(
|
||||
color: ColorsManager.graysColor,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Executed by',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color: ColorsManager.textPrimaryColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14),
|
||||
),
|
||||
Text('Cloud',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color: ColorsManager.textGray,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14)),
|
||||
],
|
||||
),
|
||||
if (context.read<RoutineBloc>().state.isUpdate ??
|
||||
false)
|
||||
const DeleteSceneWidget()
|
||||
],
|
||||
)),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10, left: 10, right: 10, bottom: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () {
|
||||
BlocProvider.of<SettingBloc>(context).add(
|
||||
FetchIcons(
|
||||
expanded: !context
|
||||
.read<SettingBloc>()
|
||||
.isExpanded));
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Icons',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color:
|
||||
ColorsManager.textPrimaryColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14),
|
||||
),
|
||||
const Icon(
|
||||
Icons.arrow_forward_ios_outlined,
|
||||
color: ColorsManager.textGray,
|
||||
size: 15,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
const Divider(
|
||||
color: ColorsManager.graysColor,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Show on devices page',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color: ColorsManager.textPrimaryColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Container(
|
||||
height: 30,
|
||||
width: 1,
|
||||
color: ColorsManager.graysColor,
|
||||
),
|
||||
Transform.scale(
|
||||
scale: .8,
|
||||
child: CupertinoSwitch(
|
||||
value: true,
|
||||
onChanged: (value) {},
|
||||
applyTheme: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
const Divider(
|
||||
color: ColorsManager.graysColor,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Executed by',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color: ColorsManager.textPrimaryColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14),
|
||||
),
|
||||
Text('Cloud',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(
|
||||
color: ColorsManager.textGray,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 14)),
|
||||
],
|
||||
),
|
||||
if (context.read<RoutineBloc>().state.isUpdate ??
|
||||
false)
|
||||
const DeleteSceneWidget()
|
||||
],
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (context.read<SettingBloc>().isExpanded && !isAutomation)
|
||||
SizedBox(
|
||||
width: 400,
|
||||
height: 150,
|
||||
child: settingState is LoadingState
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 6,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
shrinkWrap: true,
|
||||
itemCount: list.length,
|
||||
itemBuilder: (context, index) {
|
||||
final iconModel = list[index];
|
||||
return SizedBox(
|
||||
width: 35,
|
||||
height: 35,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
BlocProvider.of<SettingBloc>(context)
|
||||
.add(SelectIcon(
|
||||
iconId: iconModel.uuid,
|
||||
));
|
||||
selectedIcon = iconModel.uuid;
|
||||
},
|
||||
child: SizedBox(
|
||||
child: ClipOval(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(1),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: selectedIcon == iconModel.uuid
|
||||
? ColorsManager
|
||||
.primaryColorWithOpacity
|
||||
: Colors.transparent,
|
||||
width: 2,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Image.memory(
|
||||
iconModel.iconBytes,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
)),
|
||||
if (context.read<SettingBloc>().isExpanded && isAutomation)
|
||||
const SizedBox(height: 350, width: 400, child: EffectivePeriodView())
|
||||
],
|
||||
),
|
||||
Container(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: ColorsManager.greyColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Container(
|
||||
alignment: AlignmentDirectional.center,
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(width: 1, height: 50, color: ColorsManager.greyColor),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
if (isAutomation) {
|
||||
BlocProvider.of<RoutineBloc>(context).add(
|
||||
EffectiveTimePeriodEvent(EffectiveTime(
|
||||
start: effectPeriodState.customStartTime!,
|
||||
end: effectPeriodState.customEndTime!,
|
||||
loops: effectPeriodState.selectedDaysBinary)));
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
Navigator.of(context).pop(selectedIcon);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
alignment: AlignmentDirectional.center,
|
||||
child: Text(
|
||||
'Confirm',
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.primaryColorWithOpacity,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,386 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/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/helper/duration_format_helper.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/switch_operational_value.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/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class ThreeGangSwitchHelper {
|
||||
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog(
|
||||
BuildContext context,
|
||||
List<DeviceFunction> functions,
|
||||
AllDevicesModel? device,
|
||||
List<DeviceFunctionData>? deviceSelectedFunctions,
|
||||
String uniqueCustomId,
|
||||
bool removeComparetors,
|
||||
) async {
|
||||
List<BaseSwitchFunction> switchFunctions = functions.whereType<BaseSwitchFunction>().toList();
|
||||
|
||||
return showDialog<Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
|
||||
child: AlertDialog(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
|
||||
builder: (context, state) {
|
||||
final selectedFunction = state.selectedFunction;
|
||||
final selectedOperationName = state.selectedOperationName;
|
||||
final selectedFunctionData =
|
||||
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
|
||||
orElse: () => DeviceFunctionData(
|
||||
entityId: '',
|
||||
functionCode: selectedFunction ?? '',
|
||||
operationName: '',
|
||||
value: null,
|
||||
));
|
||||
return Container(
|
||||
width: selectedFunction != null ? 600 : 360,
|
||||
height: 450,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('3 Gangs Light Switch Condition'),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
// Left side: Function list
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
itemCount: switchFunctions.length,
|
||||
separatorBuilder: (_, __) => const Divider(
|
||||
color: ColorsManager.dividerColor,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final function = switchFunctions[index];
|
||||
return ListTile(
|
||||
leading: SvgPicture.asset(
|
||||
function.icon,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
title: Text(
|
||||
function.operationName,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
trailing: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
context.read<FunctionBloc>().add(SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName: function.operationName,
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// Right side: Value selector
|
||||
if (selectedFunction != null)
|
||||
Expanded(
|
||||
child: _buildValueSelector(
|
||||
context: context,
|
||||
selectedFunction: selectedFunction,
|
||||
selectedFunctionData: selectedFunctionData,
|
||||
switchFunctions: switchFunctions,
|
||||
device: device,
|
||||
operationName: selectedOperationName ?? '',
|
||||
removeComparetors: removeComparetors,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildValueSelector({
|
||||
required BuildContext context,
|
||||
required String selectedFunction,
|
||||
required DeviceFunctionData? selectedFunctionData,
|
||||
required List<BaseSwitchFunction> switchFunctions,
|
||||
AllDevicesModel? device,
|
||||
required String operationName,
|
||||
required bool removeComparetors,
|
||||
}) {
|
||||
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,
|
||||
removeComparetors: removeComparetors,
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
bool? removeComparetors,
|
||||
}) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
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, 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,
|
||||
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(
|
||||
BuildContext context,
|
||||
dynamic initialValue,
|
||||
AllDevicesModel? device,
|
||||
String operationName,
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
String selectCode,
|
||||
) {
|
||||
final operationalValues = SwitchOperationalValue(
|
||||
icon: '',
|
||||
description: "sec",
|
||||
value: 0.0,
|
||||
minValue: 0,
|
||||
maxValue: 86400,
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,384 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/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/helper/duration_format_helper.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/switch_operational_value.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/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class TwoGangSwitchHelper {
|
||||
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog(
|
||||
BuildContext context,
|
||||
List<DeviceFunction> functions,
|
||||
AllDevicesModel? device,
|
||||
List<DeviceFunctionData>? deviceSelectedFunctions,
|
||||
String uniqueCustomId,
|
||||
bool removeComparetors,
|
||||
) async {
|
||||
List<BaseSwitchFunction> switchFunctions = functions.whereType<BaseSwitchFunction>().toList();
|
||||
|
||||
return showDialog<Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => FunctionBloc()..add(InitializeFunctions(deviceSelectedFunctions ?? [])),
|
||||
child: AlertDialog(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
content: BlocBuilder<FunctionBloc, FunctionBlocState>(
|
||||
builder: (context, state) {
|
||||
final selectedFunction = state.selectedFunction;
|
||||
final selectedOperationName = state.selectedOperationName;
|
||||
final selectedFunctionData =
|
||||
state.addedFunctions.firstWhere((f) => f.functionCode == selectedFunction,
|
||||
orElse: () => DeviceFunctionData(
|
||||
entityId: '',
|
||||
functionCode: selectedFunction ?? '',
|
||||
operationName: '',
|
||||
value: null,
|
||||
));
|
||||
return Container(
|
||||
width: selectedFunction != null ? 600 : 360,
|
||||
height: 450,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const DialogHeader('2 Gangs Light Switch Condition'),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
// Left side: Function list
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
itemCount: switchFunctions.length,
|
||||
separatorBuilder: (_, __) => const Divider(
|
||||
color: ColorsManager.dividerColor,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final function = switchFunctions[index];
|
||||
return ListTile(
|
||||
leading: SvgPicture.asset(
|
||||
function.icon,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
title: Text(
|
||||
function.operationName,
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
trailing: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: ColorsManager.textGray,
|
||||
),
|
||||
onTap: () {
|
||||
context.read<FunctionBloc>().add(SelectFunction(
|
||||
functionCode: function.code,
|
||||
operationName: function.operationName,
|
||||
));
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// Right side: Value selector
|
||||
if (selectedFunction != null)
|
||||
Expanded(
|
||||
child: _buildValueSelector(
|
||||
context: context,
|
||||
selectedFunction: selectedFunction,
|
||||
selectedFunctionData: selectedFunctionData,
|
||||
switchFunctions: switchFunctions,
|
||||
device: device,
|
||||
operationName: selectedOperationName ?? '',
|
||||
removeComparetors: removeComparetors,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Widget _buildValueSelector({
|
||||
required BuildContext context,
|
||||
required String selectedFunction,
|
||||
required DeviceFunctionData? selectedFunctionData,
|
||||
required List<BaseSwitchFunction> switchFunctions,
|
||||
AllDevicesModel? device,
|
||||
required String operationName,
|
||||
required bool removeComparetors,
|
||||
}) {
|
||||
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,
|
||||
removeComparetors: removeComparetors,
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
bool? removeComparetors,
|
||||
}) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
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, 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,
|
||||
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(
|
||||
BuildContext context,
|
||||
dynamic initialValue,
|
||||
AllDevicesModel? device,
|
||||
String operationName,
|
||||
DeviceFunctionData? selectedFunctionData,
|
||||
String selectCode,
|
||||
) {
|
||||
final operationalValues = SwitchOperationalValue(
|
||||
icon: '',
|
||||
description: "sec",
|
||||
value: 0.0,
|
||||
minValue: 0,
|
||||
maxValue: 86400,
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user