mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 07:07:19 +00:00
push value notifers
This commit is contained in:
@ -6,6 +6,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||||
|
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
import 'package:syncrow_web/pages/visitor_password/bloc/visitor_password_bloc.dart';
|
||||||
import 'package:syncrow_web/services/locator.dart';
|
import 'package:syncrow_web/services/locator.dart';
|
||||||
import 'package:syncrow_web/utils/app_routes.dart';
|
import 'package:syncrow_web/utils/app_routes.dart';
|
||||||
@ -14,7 +15,8 @@ import 'package:syncrow_web/utils/theme/theme.dart';
|
|||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
try {
|
try {
|
||||||
const environment = String.fromEnvironment('FLAVOR', defaultValue: 'development');
|
const environment =
|
||||||
|
String.fromEnvironment('FLAVOR', defaultValue: 'development');
|
||||||
await dotenv.load(fileName: '.env.$environment');
|
await dotenv.load(fileName: '.env.$environment');
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
initialSetup();
|
initialSetup();
|
||||||
@ -46,10 +48,14 @@ class MyApp extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
BlocProvider(
|
||||||
|
create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||||
BlocProvider<VisitorPasswordBloc>(
|
BlocProvider<VisitorPasswordBloc>(
|
||||||
create: (context) => VisitorPasswordBloc(),
|
create: (context) => VisitorPasswordBloc(),
|
||||||
)
|
),
|
||||||
|
BlocProvider<RoutineBloc>(
|
||||||
|
create: (context) => RoutineBloc(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: MaterialApp.router(
|
child: MaterialApp.router(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
|
@ -1,219 +1,330 @@
|
|||||||
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/routiens/bloc/routine_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/routiens/models/ac/ac_function.dart';
|
import 'package:syncrow_web/pages/routiens/models/ac/ac_function.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/widgets/dialog_footer.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';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
class ACHelper {
|
class ACHelper {
|
||||||
static Future<void> showACFunctionsDialog(
|
static Future<Map<String, dynamic>?> showACFunctionsDialog(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
List<DeviceFunction<dynamic>> functions,
|
List<DeviceFunction<dynamic>> functions,
|
||||||
) async {
|
) async {
|
||||||
List<ACFunction> acFunctions = functions.whereType<ACFunction>().toList();
|
List<ACFunction> acFunctions = functions.whereType<ACFunction>().toList();
|
||||||
// Track multiple selections using a map
|
String? selectedFunction;
|
||||||
Map<String, dynamic> selectedValues = {};
|
dynamic selectedValue = 20;
|
||||||
List<DeviceFunctionData> selectedFunctions = [];
|
String? selectedCondition = "==";
|
||||||
|
List<bool> _selectedConditions = [false, true, false];
|
||||||
|
|
||||||
await showDialog(
|
return showDialog<Map<String, dynamic>?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return Dialog(
|
return StatefulBuilder(
|
||||||
child: Container(
|
builder: (context, setState) {
|
||||||
width: 600,
|
return AlertDialog(
|
||||||
height: 450,
|
contentPadding: EdgeInsets.zero,
|
||||||
decoration: BoxDecoration(
|
content: _buildDialogContent(
|
||||||
color: Colors.white,
|
context,
|
||||||
borderRadius: BorderRadius.circular(20),
|
setState,
|
||||||
),
|
acFunctions,
|
||||||
padding: const EdgeInsets.only(top: 20),
|
selectedFunction,
|
||||||
child: Column(
|
selectedValue,
|
||||||
mainAxisSize: MainAxisSize.min,
|
selectedCondition,
|
||||||
children: [
|
_selectedConditions,
|
||||||
Text(
|
(fn) => selectedFunction = fn,
|
||||||
'AC Functions',
|
(val) => selectedValue = val,
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
(cond) => selectedCondition = cond,
|
||||||
color: ColorsManager.primaryColorWithOpacity,
|
),
|
||||||
fontWeight: FontWeight.bold,
|
);
|
||||||
),
|
},
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(vertical: 15, horizontal: 50),
|
|
||||||
child: Container(
|
|
||||||
height: 1,
|
|
||||||
width: double.infinity,
|
|
||||||
color: ColorsManager.greyColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ListView.separated(
|
|
||||||
itemCount: acFunctions.length,
|
|
||||||
separatorBuilder: (_, __) => const Divider(
|
|
||||||
color: ColorsManager.dividerColor,
|
|
||||||
),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final function = acFunctions[index];
|
|
||||||
final isSelected =
|
|
||||||
selectedValues.containsKey(function.code);
|
|
||||||
return ListTile(
|
|
||||||
tileColor:
|
|
||||||
isSelected ? Colors.grey.shade100 : null,
|
|
||||||
leading: SvgPicture.asset(
|
|
||||||
function.icon,
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
function.operationName,
|
|
||||||
style: context.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
trailing: isSelected
|
|
||||||
? Icon(
|
|
||||||
Icons.check_circle,
|
|
||||||
color:
|
|
||||||
ColorsManager.primaryColorWithOpacity,
|
|
||||||
size: 20,
|
|
||||||
)
|
|
||||||
: const Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
|
||||||
size: 16,
|
|
||||||
color: ColorsManager.textGray,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
if (isSelected) {
|
|
||||||
selectedValues.remove(function.code);
|
|
||||||
selectedFunctions.removeWhere(
|
|
||||||
(f) => f.function == function.code);
|
|
||||||
}
|
|
||||||
(context as Element).markNeedsBuild();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Builder(
|
|
||||||
builder: (context) {
|
|
||||||
final selectedFunction = acFunctions.firstWhere(
|
|
||||||
(f) => selectedValues.containsKey(f.code),
|
|
||||||
orElse: () => acFunctions.first,
|
|
||||||
);
|
|
||||||
return _buildValueSelector(
|
|
||||||
context,
|
|
||||||
selectedFunction,
|
|
||||||
selectedValues[selectedFunction.code],
|
|
||||||
(value) {
|
|
||||||
selectedValues[selectedFunction.code] = value;
|
|
||||||
// Update or add the function data
|
|
||||||
final functionData = DeviceFunctionData(
|
|
||||||
entityId: selectedFunction.deviceId,
|
|
||||||
function: selectedFunction.code,
|
|
||||||
operationName: selectedFunction.operationName,
|
|
||||||
value: value,
|
|
||||||
valueDescription: _getValueDescription(
|
|
||||||
selectedFunction, value),
|
|
||||||
);
|
|
||||||
|
|
||||||
final existingIndex =
|
|
||||||
selectedFunctions.indexWhere((f) =>
|
|
||||||
f.function == selectedFunction.code);
|
|
||||||
if (existingIndex != -1) {
|
|
||||||
selectedFunctions[existingIndex] =
|
|
||||||
functionData;
|
|
||||||
} else {
|
|
||||||
selectedFunctions.add(functionData);
|
|
||||||
}
|
|
||||||
|
|
||||||
(context as Element).markNeedsBuild();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 1,
|
|
||||||
width: double.infinity,
|
|
||||||
color: ColorsManager.greyColor,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: Text(
|
|
||||||
'Cancel',
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyMedium!
|
|
||||||
.copyWith(color: ColorsManager.greyColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: selectedFunctions.isNotEmpty
|
|
||||||
? () {
|
|
||||||
// Add all selected functions to the bloc
|
|
||||||
for (final function in selectedFunctions) {
|
|
||||||
context
|
|
||||||
.read<RoutineBloc>()
|
|
||||||
.add(AddFunction(function));
|
|
||||||
}
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
child: Text(
|
|
||||||
'Confirm',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
||||||
color: ColorsManager.primaryColorWithOpacity,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Widget _buildValueSelector(
|
/// Build dialog content for AC functions dialog
|
||||||
|
static Widget _buildDialogContent(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
ACFunction function,
|
StateSetter setState,
|
||||||
|
List<ACFunction> acFunctions,
|
||||||
|
String? selectedFunction,
|
||||||
dynamic selectedValue,
|
dynamic selectedValue,
|
||||||
|
String? selectedCondition,
|
||||||
|
List<bool> selectedConditions,
|
||||||
|
Function(String?) onFunctionSelected,
|
||||||
Function(dynamic) onValueSelected,
|
Function(dynamic) onValueSelected,
|
||||||
|
Function(String?) onConditionSelected,
|
||||||
) {
|
) {
|
||||||
final values = function.getOperationalValues();
|
|
||||||
return Container(
|
return Container(
|
||||||
height: 200,
|
width: selectedFunction != null ? 600 : 360,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
height: 450,
|
||||||
child: ListView.builder(
|
decoration: BoxDecoration(
|
||||||
itemCount: values.length,
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.only(top: 20),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const DialogHeader('AC Functions'),
|
||||||
|
Flexible(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_buildFunctionsList(
|
||||||
|
context,
|
||||||
|
setState,
|
||||||
|
acFunctions,
|
||||||
|
selectedFunction,
|
||||||
|
onFunctionSelected,
|
||||||
|
),
|
||||||
|
if (selectedFunction != null)
|
||||||
|
_buildValueSelector(
|
||||||
|
context,
|
||||||
|
setState,
|
||||||
|
selectedFunction,
|
||||||
|
selectedValue,
|
||||||
|
selectedCondition,
|
||||||
|
selectedConditions,
|
||||||
|
onValueSelected,
|
||||||
|
onConditionSelected,
|
||||||
|
acFunctions,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DialogFooter(
|
||||||
|
onCancel: () => Navigator.pop(context),
|
||||||
|
onConfirm: selectedFunction != null && selectedValue != null
|
||||||
|
? () => Navigator.pop(context, {
|
||||||
|
'function': selectedFunction,
|
||||||
|
'value': selectedValue,
|
||||||
|
'condition': selectedCondition ?? "==",
|
||||||
|
})
|
||||||
|
: null,
|
||||||
|
isConfirmEnabled: selectedFunction != null && selectedValue != null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build functions list for AC functions dialog
|
||||||
|
static Widget _buildFunctionsList(
|
||||||
|
BuildContext context,
|
||||||
|
StateSetter setState,
|
||||||
|
List<ACFunction> acFunctions,
|
||||||
|
String? selectedFunction,
|
||||||
|
Function(String?) onFunctionSelected,
|
||||||
|
) {
|
||||||
|
return Expanded(
|
||||||
|
child: ListView.separated(
|
||||||
|
shrinkWrap: false,
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
itemCount: acFunctions.length,
|
||||||
|
separatorBuilder: (context, index) => const Divider(
|
||||||
|
color: ColorsManager.dividerColor,
|
||||||
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final value = values[index];
|
final function = acFunctions[index];
|
||||||
return RadioListTile<dynamic>(
|
return ListTile(
|
||||||
value: value.value,
|
leading: SvgPicture.asset(
|
||||||
groupValue: selectedValue,
|
function.icon,
|
||||||
onChanged: onValueSelected,
|
width: 24,
|
||||||
title: Text(value.description),
|
height: 24,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
function.operationName,
|
||||||
|
style: context.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
trailing: const Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
size: 16,
|
||||||
|
color: ColorsManager.textGray,
|
||||||
|
),
|
||||||
|
onTap: () => setState(() => onFunctionSelected(function.code)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String _getValueDescription(ACFunction function, dynamic value) {
|
/// Build value selector for AC functions dialog
|
||||||
final values = function.getOperationalValues();
|
static Widget _buildValueSelector(
|
||||||
final selectedValue = values.firstWhere((v) => v.value == value);
|
BuildContext context,
|
||||||
return selectedValue.description;
|
StateSetter setState,
|
||||||
|
String selectedFunction,
|
||||||
|
dynamic selectedValue,
|
||||||
|
String? selectedCondition,
|
||||||
|
List<bool> selectedConditions,
|
||||||
|
Function(dynamic) onValueSelected,
|
||||||
|
Function(String?) onConditionSelected,
|
||||||
|
List<ACFunction> acFunctions,
|
||||||
|
) {
|
||||||
|
if (selectedFunction == 'temp_set' || selectedFunction == 'temp_current') {
|
||||||
|
return Expanded(
|
||||||
|
child: _buildTemperatureSelector(
|
||||||
|
context,
|
||||||
|
setState,
|
||||||
|
selectedValue,
|
||||||
|
selectedCondition,
|
||||||
|
selectedConditions,
|
||||||
|
onValueSelected,
|
||||||
|
onConditionSelected,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final selectedFn =
|
||||||
|
acFunctions.firstWhere((f) => f.code == selectedFunction);
|
||||||
|
final values = selectedFn.getOperationalValues();
|
||||||
|
return Expanded(
|
||||||
|
child: _buildOperationalValuesList(
|
||||||
|
context,
|
||||||
|
setState,
|
||||||
|
values,
|
||||||
|
selectedValue,
|
||||||
|
onValueSelected,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build temperature selector for AC functions dialog
|
||||||
|
static Widget _buildTemperatureSelector(
|
||||||
|
BuildContext context,
|
||||||
|
StateSetter setState,
|
||||||
|
dynamic selectedValue,
|
||||||
|
String? selectedCondition,
|
||||||
|
List<bool> selectedConditions,
|
||||||
|
Function(dynamic) onValueSelected,
|
||||||
|
Function(String?) onConditionSelected,
|
||||||
|
) {
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
_buildConditionToggle(
|
||||||
|
context,
|
||||||
|
setState,
|
||||||
|
selectedConditions,
|
||||||
|
onConditionSelected,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
_buildTemperatureDisplay(context, selectedValue),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
_buildTemperatureSlider(
|
||||||
|
context,
|
||||||
|
setState,
|
||||||
|
selectedValue,
|
||||||
|
onValueSelected,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build condition toggle for AC functions dialog
|
||||||
|
static Widget _buildConditionToggle(
|
||||||
|
BuildContext context,
|
||||||
|
StateSetter setState,
|
||||||
|
List<bool> selectedConditions,
|
||||||
|
Function(String?) onConditionSelected,
|
||||||
|
) {
|
||||||
|
return ToggleButtons(
|
||||||
|
onPressed: (int index) {
|
||||||
|
setState(() {
|
||||||
|
for (int i = 0; i < selectedConditions.length; i++) {
|
||||||
|
selectedConditions[i] = i == index;
|
||||||
|
}
|
||||||
|
onConditionSelected(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(">")],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build temperature display for AC functions dialog
|
||||||
|
static Widget _buildTemperatureDisplay(
|
||||||
|
BuildContext context, dynamic selectedValue) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.primaryColorWithOpacity.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${selectedValue ?? 20}°C',
|
||||||
|
style: context.textTheme.headlineMedium!.copyWith(
|
||||||
|
color: ColorsManager.primaryColorWithOpacity,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget _buildTemperatureSlider(
|
||||||
|
BuildContext context,
|
||||||
|
StateSetter setState,
|
||||||
|
dynamic selectedValue,
|
||||||
|
Function(dynamic) onValueSelected,
|
||||||
|
) {
|
||||||
|
final currentValue = selectedValue is int ? selectedValue.toDouble() : 20.0;
|
||||||
|
return Slider(
|
||||||
|
value: currentValue,
|
||||||
|
min: 16,
|
||||||
|
max: 30,
|
||||||
|
divisions: 14,
|
||||||
|
label: '${currentValue.toInt()}°C',
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() => onValueSelected(value.toInt()));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Widget _buildOperationalValuesList(
|
||||||
|
BuildContext context,
|
||||||
|
StateSetter setState,
|
||||||
|
List<dynamic> values,
|
||||||
|
dynamic selectedValue,
|
||||||
|
Function(dynamic) onValueSelected,
|
||||||
|
) {
|
||||||
|
return ListView.builder(
|
||||||
|
shrinkWrap: false,
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
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) {
|
||||||
|
setState(() => onValueSelected(newValue));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,38 @@
|
|||||||
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/routiens/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/one_gang_switch/one_gang_switch.dart';
|
import 'package:syncrow_web/pages/routiens/models/gang_switches/one_gang_switch/one_gang_switch.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/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
class OneGangSwitchHelper {
|
class OneGangSwitchHelper {
|
||||||
static Future<void> showSwitchFunctionsDialog(
|
static Future<Map<String, dynamic>?> showSwitchFunctionsDialog(
|
||||||
BuildContext context, List<DeviceFunction<dynamic>> functions) async {
|
BuildContext context,
|
||||||
|
List<DeviceFunction<dynamic>> functions,
|
||||||
|
) async {
|
||||||
List<DeviceFunction<dynamic>> switchFunctions = functions
|
List<DeviceFunction<dynamic>> switchFunctions = functions
|
||||||
.where(
|
.where(
|
||||||
(f) => f is OneGangSwitchFunction || f is OneGangCountdownFunction)
|
(f) => f is OneGangSwitchFunction || f is OneGangCountdownFunction)
|
||||||
.toList();
|
.toList();
|
||||||
Map<String, dynamic> selectedValues = {};
|
final selectedFunctionNotifier = ValueNotifier<String?>(null);
|
||||||
List<DeviceFunctionData> selectedFunctions = [];
|
final selectedValueNotifier = ValueNotifier<dynamic>(null);
|
||||||
String? selectedCondition = "<";
|
final selectedConditionNotifier = ValueNotifier<String?>("<");
|
||||||
List<bool> selectedConditions = [true, false, false];
|
final selectedConditionsNotifier =
|
||||||
|
ValueNotifier<List<bool>>([true, false, false]);
|
||||||
|
|
||||||
await showDialog(
|
return showDialog<Map<String, dynamic>?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return StatefulBuilder(
|
return ValueListenableBuilder<String?>(
|
||||||
builder: (context, setState) {
|
valueListenable: selectedFunctionNotifier,
|
||||||
|
builder: (context, selectedFunction, _) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
content: Container(
|
content: Container(
|
||||||
width: 600,
|
width: selectedFunction != null ? 600 : 360,
|
||||||
height: 450,
|
height: 450,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@ -38,22 +42,7 @@ class OneGangSwitchHelper {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
const DialogHeader('1 Gang Light Switch Condition'),
|
||||||
'1 Gang Light Switch Condition',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
||||||
color: ColorsManager.primaryColorWithOpacity,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 15, horizontal: 50),
|
|
||||||
child: Container(
|
|
||||||
height: 1,
|
|
||||||
width: double.infinity,
|
|
||||||
color: ColorsManager.greyColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@ -66,143 +55,67 @@ class OneGangSwitchHelper {
|
|||||||
),
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final function = switchFunctions[index];
|
final function = switchFunctions[index];
|
||||||
final isSelected =
|
return ValueListenableBuilder<String?>(
|
||||||
selectedValues.containsKey(function.code);
|
valueListenable: selectedFunctionNotifier,
|
||||||
return ListTile(
|
builder: (context, selectedFunction, _) {
|
||||||
tileColor:
|
final isSelected =
|
||||||
isSelected ? Colors.grey.shade100 : null,
|
selectedFunction == function.code;
|
||||||
leading: SvgPicture.asset(
|
|
||||||
function.icon,
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
function.operationName,
|
|
||||||
style: context.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
trailing: isSelected
|
|
||||||
? Icon(
|
|
||||||
Icons.check_circle,
|
|
||||||
color: ColorsManager
|
|
||||||
.primaryColorWithOpacity,
|
|
||||||
size: 20,
|
|
||||||
)
|
|
||||||
: const Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
|
||||||
size: 16,
|
|
||||||
color: ColorsManager.textGray,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
if (isSelected) {
|
|
||||||
selectedValues.remove(function.code);
|
|
||||||
selectedFunctions.removeWhere(
|
|
||||||
(f) => f.function == function.code);
|
|
||||||
}
|
|
||||||
(context as Element).markNeedsBuild();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Right side: Value selector
|
|
||||||
Expanded(
|
|
||||||
child: Builder(
|
|
||||||
builder: (context) {
|
|
||||||
final selectedFn = switchFunctions.firstWhere(
|
|
||||||
(f) => selectedValues.containsKey(f.code),
|
|
||||||
orElse: () => switchFunctions.first,
|
|
||||||
) as BaseSwitchFunction;
|
|
||||||
|
|
||||||
if (selectedFn is OneGangCountdownFunction) {
|
|
||||||
return _buildCountDownSelector(
|
|
||||||
context,
|
|
||||||
setState,
|
|
||||||
selectedValues[selectedFn.code] ?? 0,
|
|
||||||
selectedCondition,
|
|
||||||
selectedConditions,
|
|
||||||
(value) {
|
|
||||||
selectedValues[selectedFn.code] = value;
|
|
||||||
final functionData = DeviceFunctionData(
|
|
||||||
entityId: selectedFn.deviceId,
|
|
||||||
function: selectedFn.code,
|
|
||||||
operationName: selectedFn.operationName,
|
|
||||||
value: value,
|
|
||||||
condition: selectedCondition,
|
|
||||||
valueDescription: '${value} sec',
|
|
||||||
);
|
|
||||||
|
|
||||||
final existingIndex =
|
|
||||||
selectedFunctions.indexWhere((f) =>
|
|
||||||
f.function == selectedFn.code);
|
|
||||||
if (existingIndex != -1) {
|
|
||||||
selectedFunctions[existingIndex] =
|
|
||||||
functionData;
|
|
||||||
} else {
|
|
||||||
selectedFunctions.add(functionData);
|
|
||||||
}
|
|
||||||
(context as Element).markNeedsBuild();
|
|
||||||
},
|
|
||||||
(condition) {
|
|
||||||
setState(() {
|
|
||||||
selectedCondition = condition;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final values =
|
|
||||||
selectedFn.getOperationalValues();
|
|
||||||
return ListView.builder(
|
|
||||||
itemCount: values.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final value = values[index];
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
tileColor: isSelected
|
||||||
|
? Colors.grey.shade100
|
||||||
|
: null,
|
||||||
leading: SvgPicture.asset(
|
leading: SvgPicture.asset(
|
||||||
value.icon,
|
function.icon,
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
value.description,
|
function.operationName,
|
||||||
style: context.textTheme.bodyMedium,
|
style: context.textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
trailing: Radio<dynamic>(
|
trailing: const Icon(
|
||||||
value: value.value,
|
Icons.arrow_forward_ios,
|
||||||
groupValue:
|
size: 16,
|
||||||
selectedValues[selectedFn.code],
|
color: ColorsManager.textGray,
|
||||||
onChanged: (newValue) {
|
|
||||||
selectedValues[selectedFn.code] =
|
|
||||||
newValue;
|
|
||||||
final functionData =
|
|
||||||
DeviceFunctionData(
|
|
||||||
entityId: selectedFn.deviceId,
|
|
||||||
function: selectedFn.code,
|
|
||||||
operationName:
|
|
||||||
selectedFn.operationName,
|
|
||||||
value: newValue,
|
|
||||||
valueDescription: value.description,
|
|
||||||
);
|
|
||||||
|
|
||||||
final existingIndex =
|
|
||||||
selectedFunctions.indexWhere(
|
|
||||||
(f) =>
|
|
||||||
f.function ==
|
|
||||||
selectedFn.code);
|
|
||||||
if (existingIndex != -1) {
|
|
||||||
selectedFunctions[existingIndex] =
|
|
||||||
functionData;
|
|
||||||
} else {
|
|
||||||
selectedFunctions.add(functionData);
|
|
||||||
}
|
|
||||||
(context as Element).markNeedsBuild();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
onTap: () {
|
||||||
|
selectedFunctionNotifier.value =
|
||||||
|
function.code;
|
||||||
|
selectedValueNotifier.value =
|
||||||
|
function is OneGangCountdownFunction
|
||||||
|
? 0
|
||||||
|
: null;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// Right side: Value selector
|
||||||
|
if (selectedFunction != null)
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -211,41 +124,36 @@ class OneGangSwitchHelper {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
color: ColorsManager.greyColor,
|
color: ColorsManager.greyColor,
|
||||||
),
|
),
|
||||||
Row(
|
DialogFooter(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
onCancel: () => Navigator.pop(context),
|
||||||
children: [
|
onConfirm: selectedFunctionNotifier.value != null &&
|
||||||
TextButton(
|
selectedValueNotifier.value != null
|
||||||
onPressed: () => Navigator.pop(context),
|
? () {
|
||||||
child: Text(
|
final selectedFn = switchFunctions.firstWhere(
|
||||||
'Cancel',
|
(f) => f.code == selectedFunctionNotifier.value,
|
||||||
style: Theme.of(context)
|
);
|
||||||
.textTheme
|
final value = selectedValueNotifier.value;
|
||||||
.bodyMedium!
|
final functionData = DeviceFunctionData(
|
||||||
.copyWith(color: ColorsManager.greyColor),
|
entityId: selectedFn.deviceId,
|
||||||
),
|
function: selectedFn.code,
|
||||||
),
|
operationName: selectedFn.operationName,
|
||||||
TextButton(
|
value: value,
|
||||||
onPressed: selectedFunctions.isNotEmpty
|
condition: selectedConditionNotifier.value,
|
||||||
? () {
|
valueDescription:
|
||||||
for (final function in selectedFunctions) {
|
selectedFn is OneGangCountdownFunction
|
||||||
context
|
? '${value} sec'
|
||||||
.read<RoutineBloc>()
|
: ((selectedFn as BaseSwitchFunction)
|
||||||
.add(AddFunction(function));
|
.getOperationalValues()
|
||||||
}
|
.firstWhere((v) => v.value == value)
|
||||||
Navigator.pop(context, true);
|
.description),
|
||||||
}
|
);
|
||||||
: null,
|
Navigator.pop(
|
||||||
child: Text(
|
context, {selectedFn.code: functionData});
|
||||||
'Confirm',
|
}
|
||||||
style: Theme.of(context)
|
: null,
|
||||||
.textTheme
|
isConfirmEnabled:
|
||||||
.bodyMedium!
|
selectedFunctionNotifier.value != null &&
|
||||||
.copyWith(
|
selectedValueNotifier.value != null,
|
||||||
color: ColorsManager.primaryColorWithOpacity,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -257,77 +165,101 @@ class OneGangSwitchHelper {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build countdown selector for switch functions dialog
|
|
||||||
static Widget _buildCountDownSelector(
|
static Widget _buildCountDownSelector(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
StateSetter setState,
|
ValueNotifier<dynamic> valueNotifier,
|
||||||
dynamic selectedValue,
|
ValueNotifier<String?> conditionNotifier,
|
||||||
String? selectedCondition,
|
ValueNotifier<List<bool>> conditionsNotifier,
|
||||||
List<bool> selectedConditions,
|
|
||||||
Function(dynamic) onValueSelected,
|
|
||||||
Function(String?) onConditionSelected,
|
|
||||||
) {
|
) {
|
||||||
return Column(
|
return ValueListenableBuilder<dynamic>(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
valueListenable: valueNotifier,
|
||||||
children: [
|
builder: (context, value, _) {
|
||||||
_buildConditionToggle(
|
return Column(
|
||||||
context,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
setState,
|
children: [
|
||||||
selectedConditions,
|
ValueListenableBuilder<List<bool>>(
|
||||||
onConditionSelected,
|
valueListenable: conditionsNotifier,
|
||||||
),
|
builder: (context, selectedConditions, _) {
|
||||||
const SizedBox(height: 20),
|
return ToggleButtons(
|
||||||
Text(
|
onPressed: (int index) {
|
||||||
'${selectedValue.toString()} sec',
|
final newConditions = List<bool>.filled(3, false);
|
||||||
style: Theme.of(context).textTheme.headlineMedium,
|
newConditions[index] = true;
|
||||||
),
|
conditionsNotifier.value = newConditions;
|
||||||
const SizedBox(height: 20),
|
conditionNotifier.value = index == 0
|
||||||
Slider(
|
? "<"
|
||||||
value: selectedValue.toDouble(),
|
: index == 1
|
||||||
min: 0,
|
? "=="
|
||||||
max: 300, // 5 minutes in seconds
|
: ">";
|
||||||
divisions: 300,
|
},
|
||||||
onChanged: (value) {
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
setState(() {
|
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
|
||||||
onValueSelected(value.toInt());
|
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();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build condition toggle for AC functions dialog
|
static Widget _buildOperationalValuesList(
|
||||||
static Widget _buildConditionToggle(
|
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
StateSetter setState,
|
BaseSwitchFunction function,
|
||||||
List<bool> selectedConditions,
|
ValueNotifier<dynamic> valueNotifier,
|
||||||
Function(String?) onConditionSelected,
|
|
||||||
) {
|
) {
|
||||||
return ToggleButtons(
|
final values = function.getOperationalValues();
|
||||||
onPressed: (int index) {
|
return ValueListenableBuilder<dynamic>(
|
||||||
setState(() {
|
valueListenable: valueNotifier,
|
||||||
for (int i = 0; i < selectedConditions.length; i++) {
|
builder: (context, selectedValue, _) {
|
||||||
selectedConditions[i] = i == index;
|
return ListView.builder(
|
||||||
}
|
itemCount: values.length,
|
||||||
onConditionSelected(index == 0
|
itemBuilder: (context, index) {
|
||||||
? "<"
|
final value = values[index];
|
||||||
: index == 1
|
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;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
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(">")],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import 'package:syncrow_web/pages/routiens/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/two_gang_switch/two_gang_switch.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/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
@ -18,20 +20,23 @@ class TwoGangSwitchHelper {
|
|||||||
f is TwoGangCountdown1Function ||
|
f is TwoGangCountdown1Function ||
|
||||||
f is TwoGangCountdown2Function)
|
f is TwoGangCountdown2Function)
|
||||||
.toList();
|
.toList();
|
||||||
Map<String, dynamic> selectedValues = {};
|
|
||||||
List<DeviceFunctionData> selectedFunctions = [];
|
final selectedFunctionNotifier = ValueNotifier<String?>(null);
|
||||||
String? selectedCondition = "<";
|
final selectedValueNotifier = ValueNotifier<dynamic>(null);
|
||||||
List<bool> selectedConditions = [true, false, false];
|
final selectedConditionNotifier = ValueNotifier<String?>('<');
|
||||||
|
final selectedConditionsNotifier =
|
||||||
|
ValueNotifier<List<bool>>([true, false, false]);
|
||||||
|
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return StatefulBuilder(
|
return ValueListenableBuilder<String?>(
|
||||||
builder: (context, setState) {
|
valueListenable: selectedFunctionNotifier,
|
||||||
|
builder: (context, selectedFunction, _) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
content: Container(
|
content: Container(
|
||||||
width: 600,
|
width: selectedFunction != null ? 600 : 300,
|
||||||
height: 450,
|
height: 450,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@ -41,22 +46,7 @@ class TwoGangSwitchHelper {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
const DialogHeader('2 Gangs Light Switch Condition'),
|
||||||
'2 Gangs Light Switch Condition',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
||||||
color: ColorsManager.primaryColorWithOpacity,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 15, horizontal: 50),
|
|
||||||
child: Container(
|
|
||||||
height: 1,
|
|
||||||
width: double.infinity,
|
|
||||||
color: ColorsManager.greyColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@ -69,187 +59,105 @@ class TwoGangSwitchHelper {
|
|||||||
),
|
),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final function = switchFunctions[index];
|
final function = switchFunctions[index];
|
||||||
final isSelected =
|
return ValueListenableBuilder<String?>(
|
||||||
selectedValues.containsKey(function.code);
|
valueListenable: selectedFunctionNotifier,
|
||||||
return ListTile(
|
builder: (context, selectedFunction, _) {
|
||||||
tileColor:
|
final isSelected =
|
||||||
isSelected ? Colors.grey.shade100 : null,
|
selectedFunction == function.code;
|
||||||
leading: SvgPicture.asset(
|
|
||||||
function.icon,
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
function.operationName,
|
|
||||||
style: context.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
trailing: isSelected
|
|
||||||
? Icon(
|
|
||||||
Icons.check_circle,
|
|
||||||
color: ColorsManager
|
|
||||||
.primaryColorWithOpacity,
|
|
||||||
size: 20,
|
|
||||||
)
|
|
||||||
: const Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
|
||||||
size: 16,
|
|
||||||
color: ColorsManager.textGray,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
if (isSelected) {
|
|
||||||
selectedValues.remove(function.code);
|
|
||||||
selectedFunctions.removeWhere(
|
|
||||||
(f) => f.function == function.code);
|
|
||||||
}
|
|
||||||
(context as Element).markNeedsBuild();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Right side: Value selector
|
|
||||||
Expanded(
|
|
||||||
child: Builder(
|
|
||||||
builder: (context) {
|
|
||||||
final selectedFn = switchFunctions.firstWhere(
|
|
||||||
(f) => selectedValues.containsKey(f.code),
|
|
||||||
orElse: () => switchFunctions.first,
|
|
||||||
) as BaseSwitchFunction;
|
|
||||||
|
|
||||||
if (selectedFn is TwoGangCountdown1Function ||
|
|
||||||
selectedFn is TwoGangCountdown2Function) {
|
|
||||||
return _buildCountDownSelector(
|
|
||||||
context,
|
|
||||||
setState,
|
|
||||||
selectedValues[selectedFn.code] ?? 0,
|
|
||||||
selectedCondition,
|
|
||||||
selectedConditions,
|
|
||||||
(value) {
|
|
||||||
selectedValues[selectedFn.code] = value;
|
|
||||||
final functionData = DeviceFunctionData(
|
|
||||||
entityId: selectedFn.deviceId,
|
|
||||||
function: selectedFn.code,
|
|
||||||
operationName: selectedFn.operationName,
|
|
||||||
value: value,
|
|
||||||
condition: selectedCondition,
|
|
||||||
valueDescription: '${value} sec',
|
|
||||||
);
|
|
||||||
|
|
||||||
final existingIndex =
|
|
||||||
selectedFunctions.indexWhere((f) =>
|
|
||||||
f.function == selectedFn.code);
|
|
||||||
if (existingIndex != -1) {
|
|
||||||
selectedFunctions[existingIndex] =
|
|
||||||
functionData;
|
|
||||||
} else {
|
|
||||||
selectedFunctions.add(functionData);
|
|
||||||
}
|
|
||||||
(context as Element).markNeedsBuild();
|
|
||||||
},
|
|
||||||
(condition) {
|
|
||||||
setState(() {
|
|
||||||
selectedCondition = condition;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final values =
|
|
||||||
selectedFn.getOperationalValues();
|
|
||||||
return ListView.builder(
|
|
||||||
itemCount: values.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final value = values[index];
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
tileColor: isSelected
|
||||||
|
? Colors.grey.shade100
|
||||||
|
: null,
|
||||||
leading: SvgPicture.asset(
|
leading: SvgPicture.asset(
|
||||||
value.icon,
|
function.icon,
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
value.description,
|
function.operationName,
|
||||||
style: context.textTheme.bodyMedium,
|
style: context.textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
trailing: Radio<dynamic>(
|
trailing: const Icon(
|
||||||
value: value.value,
|
Icons.arrow_forward_ios,
|
||||||
groupValue:
|
size: 16,
|
||||||
selectedValues[selectedFn.code],
|
color: ColorsManager.textGray,
|
||||||
onChanged: (newValue) {
|
|
||||||
selectedValues[selectedFn.code] =
|
|
||||||
newValue;
|
|
||||||
final functionData =
|
|
||||||
DeviceFunctionData(
|
|
||||||
entityId: selectedFn.deviceId,
|
|
||||||
function: selectedFn.code,
|
|
||||||
operationName:
|
|
||||||
selectedFn.operationName,
|
|
||||||
value: newValue,
|
|
||||||
valueDescription: value.description,
|
|
||||||
);
|
|
||||||
|
|
||||||
final existingIndex =
|
|
||||||
selectedFunctions.indexWhere(
|
|
||||||
(f) =>
|
|
||||||
f.function ==
|
|
||||||
selectedFn.code);
|
|
||||||
if (existingIndex != -1) {
|
|
||||||
selectedFunctions[existingIndex] =
|
|
||||||
functionData;
|
|
||||||
} else {
|
|
||||||
selectedFunctions.add(functionData);
|
|
||||||
}
|
|
||||||
(context as Element).markNeedsBuild();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
onTap: () {
|
||||||
|
selectedFunctionNotifier.value =
|
||||||
|
function.code;
|
||||||
|
selectedValueNotifier.value = function
|
||||||
|
is TwoGangCountdown1Function ||
|
||||||
|
function
|
||||||
|
is TwoGangCountdown2Function
|
||||||
|
? 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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
DialogFooter(
|
||||||
height: 1,
|
onCancel: () => Navigator.pop(context),
|
||||||
width: double.infinity,
|
onConfirm: selectedFunction != null &&
|
||||||
color: ColorsManager.greyColor,
|
selectedValueNotifier.value != null
|
||||||
),
|
? () {
|
||||||
Row(
|
final selectedFn = switchFunctions.firstWhere(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
(f) => f.code == selectedFunction,
|
||||||
children: [
|
);
|
||||||
TextButton(
|
final value = selectedValueNotifier.value;
|
||||||
onPressed: () => Navigator.pop(context),
|
final functionData = DeviceFunctionData(
|
||||||
child: Text(
|
entityId: selectedFn.deviceId,
|
||||||
'Cancel',
|
function: selectedFn.code,
|
||||||
style: Theme.of(context)
|
operationName: selectedFn.operationName,
|
||||||
.textTheme
|
value: value,
|
||||||
.bodyMedium!
|
condition: selectedConditionNotifier.value,
|
||||||
.copyWith(color: ColorsManager.greyColor),
|
valueDescription: selectedFn
|
||||||
),
|
is TwoGangCountdown1Function ||
|
||||||
),
|
selectedFn is TwoGangCountdown2Function
|
||||||
TextButton(
|
? '${value} sec'
|
||||||
onPressed: selectedFunctions.isNotEmpty
|
: ((selectedFn as BaseSwitchFunction)
|
||||||
? () {
|
.getOperationalValues()
|
||||||
for (final function in selectedFunctions) {
|
.firstWhere((v) => v.value == value)
|
||||||
context
|
.description),
|
||||||
.read<RoutineBloc>()
|
);
|
||||||
.add(AddFunction(function));
|
Navigator.pop(
|
||||||
}
|
context, {selectedFn.code: functionData});
|
||||||
Navigator.pop(context, true);
|
}
|
||||||
}
|
: null,
|
||||||
: null,
|
isConfirmEnabled: selectedFunction != null,
|
||||||
child: Text(
|
|
||||||
'Confirm',
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyMedium!
|
|
||||||
.copyWith(
|
|
||||||
color: ColorsManager.primaryColorWithOpacity,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -261,77 +169,101 @@ class TwoGangSwitchHelper {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build countdown selector for switch functions dialog
|
static Widget _buildOperationalValuesList(
|
||||||
static Widget _buildCountDownSelector(
|
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
StateSetter setState,
|
BaseSwitchFunction function,
|
||||||
dynamic selectedValue,
|
ValueNotifier<dynamic> valueNotifier,
|
||||||
String? selectedCondition,
|
|
||||||
List<bool> selectedConditions,
|
|
||||||
Function(dynamic) onValueSelected,
|
|
||||||
Function(String?) onConditionSelected,
|
|
||||||
) {
|
) {
|
||||||
return Column(
|
final values = function.getOperationalValues();
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
return ValueListenableBuilder<dynamic>(
|
||||||
children: [
|
valueListenable: valueNotifier,
|
||||||
_buildConditionToggle(
|
builder: (context, selectedValue, _) {
|
||||||
context,
|
return ListView.builder(
|
||||||
setState,
|
itemCount: values.length,
|
||||||
selectedConditions,
|
itemBuilder: (context, index) {
|
||||||
onConditionSelected,
|
final value = values[index];
|
||||||
),
|
return ListTile(
|
||||||
const SizedBox(height: 20),
|
leading: SvgPicture.asset(
|
||||||
Text(
|
value.icon,
|
||||||
'${selectedValue.toString()} sec',
|
width: 24,
|
||||||
style: Theme.of(context).textTheme.headlineMedium,
|
height: 24,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
title: Text(
|
||||||
Slider(
|
value.description,
|
||||||
value: selectedValue.toDouble(),
|
style: context.textTheme.bodyMedium,
|
||||||
min: 0,
|
),
|
||||||
max: 300, // 5 minutes in seconds
|
trailing: Radio<dynamic>(
|
||||||
divisions: 300,
|
value: value.value,
|
||||||
onChanged: (value) {
|
groupValue: selectedValue,
|
||||||
setState(() {
|
onChanged: (newValue) {
|
||||||
onValueSelected(value.toInt());
|
valueNotifier.value = newValue;
|
||||||
});
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
],
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build condition toggle for AC functions dialog
|
static Widget _buildCountDownSelector(
|
||||||
static Widget _buildConditionToggle(
|
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
StateSetter setState,
|
ValueNotifier<dynamic> valueNotifier,
|
||||||
List<bool> selectedConditions,
|
ValueNotifier<String?> conditionNotifier,
|
||||||
Function(String?) onConditionSelected,
|
ValueNotifier<List<bool>> conditionsNotifier,
|
||||||
) {
|
) {
|
||||||
return ToggleButtons(
|
return ValueListenableBuilder<dynamic>(
|
||||||
onPressed: (int index) {
|
valueListenable: valueNotifier,
|
||||||
setState(() {
|
builder: (context, value, _) {
|
||||||
for (int i = 0; i < selectedConditions.length; i++) {
|
return Column(
|
||||||
selectedConditions[i] = i == index;
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
}
|
children: [
|
||||||
onConditionSelected(index == 0
|
ValueListenableBuilder<List<bool>>(
|
||||||
? "<"
|
valueListenable: conditionsNotifier,
|
||||||
: index == 1
|
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();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
},
|
},
|
||||||
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(">")],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,82 +12,79 @@ class CreateNewRoutineView extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return Container(
|
||||||
create: (context) => RoutineBloc(),
|
alignment: Alignment.topLeft,
|
||||||
child: Container(
|
padding: const EdgeInsets.all(16),
|
||||||
alignment: Alignment.topLeft,
|
child: Column(
|
||||||
padding: const EdgeInsets.all(16),
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Column(
|
children: [
|
||||||
mainAxisSize: MainAxisSize.min,
|
const RoutineSearchAndButtons(),
|
||||||
children: [
|
const SizedBox(height: 20),
|
||||||
const RoutineSearchAndButtons(),
|
Flexible(
|
||||||
const SizedBox(height: 20),
|
child: Row(
|
||||||
Flexible(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
child: Row(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
Expanded(
|
||||||
children: [
|
child: Card(
|
||||||
Expanded(
|
child: Container(
|
||||||
child: Card(
|
decoration: BoxDecoration(
|
||||||
child: Container(
|
color: ColorsManager.whiteColors,
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.circular(15),
|
||||||
color: ColorsManager.whiteColors,
|
),
|
||||||
borderRadius: BorderRadius.circular(15),
|
child: const ConditionsRoutinesDevicesView()),
|
||||||
),
|
|
||||||
child: const ConditionsRoutinesDevicesView()),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(
|
),
|
||||||
width: 10,
|
const SizedBox(
|
||||||
),
|
width: 10,
|
||||||
Expanded(
|
),
|
||||||
child: Column(
|
Expanded(
|
||||||
children: [
|
child: Column(
|
||||||
/// IF Container
|
children: [
|
||||||
Expanded(
|
/// IF Container
|
||||||
child: Card(
|
Expanded(
|
||||||
margin: EdgeInsets.zero,
|
child: Card(
|
||||||
child: Container(
|
margin: EdgeInsets.zero,
|
||||||
decoration: const BoxDecoration(
|
child: Container(
|
||||||
color: ColorsManager.whiteColors,
|
decoration: const BoxDecoration(
|
||||||
borderRadius: BorderRadius.only(
|
color: ColorsManager.whiteColors,
|
||||||
topLeft: Radius.circular(15),
|
borderRadius: BorderRadius.only(
|
||||||
topRight: Radius.circular(15),
|
topLeft: Radius.circular(15),
|
||||||
),
|
topRight: Radius.circular(15),
|
||||||
),
|
),
|
||||||
child: const IfContainer(),
|
|
||||||
),
|
),
|
||||||
|
child: const IfContainer(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
),
|
||||||
height: 2,
|
Container(
|
||||||
width: double.infinity,
|
height: 2,
|
||||||
color: ColorsManager.dialogBlueTitle,
|
width: double.infinity,
|
||||||
),
|
color: ColorsManager.dialogBlueTitle,
|
||||||
|
),
|
||||||
|
|
||||||
/// THEN Container
|
/// THEN Container
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Card(
|
child: Card(
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: ColorsManager.boxColor,
|
color: ColorsManager.boxColor,
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
bottomLeft: Radius.circular(15),
|
bottomLeft: Radius.circular(15),
|
||||||
bottomRight: Radius.circular(15),
|
bottomRight: Radius.circular(15),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: const ThenContainer(),
|
|
||||||
),
|
),
|
||||||
|
child: const ThenContainer(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class RoutinesView extends StatelessWidget {
|
class RoutinesView extends StatelessWidget {
|
||||||
@ -8,54 +10,67 @@ class RoutinesView extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return BlocBuilder<SwitchTabsBloc, SwitchTabsState>(
|
||||||
padding: const EdgeInsets.all(16),
|
builder: (context, state) {
|
||||||
child: Column(
|
if (state is ShowCreateRoutineState && state.showCreateRoutine) {
|
||||||
mainAxisSize: MainAxisSize.min,
|
return const CreateNewRoutineView();
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
}
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
return Padding(
|
||||||
children: [
|
padding: const EdgeInsets.all(16),
|
||||||
Text("Create New Routines",
|
child: Column(
|
||||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
mainAxisSize: MainAxisSize.min,
|
||||||
fontWeight: FontWeight.bold,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
color: ColorsManager.grayColor,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
)),
|
children: [
|
||||||
SizedBox(
|
Text(
|
||||||
height: 200,
|
"Create New Routines",
|
||||||
width: 150,
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
child: GestureDetector(
|
fontWeight: FontWeight.bold,
|
||||||
onTap: () {
|
color: ColorsManager.grayColor,
|
||||||
BlocProvider.of<SwitchTabsBloc>(context).add(
|
),
|
||||||
const CreateNewRoutineViewEvent(true),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Card(
|
|
||||||
elevation: 3,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
color: ColorsManager.whiteColors,
|
|
||||||
child: Center(
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: ColorsManager.graysColor,
|
|
||||||
borderRadius: BorderRadius.circular(120),
|
|
||||||
border: Border.all(color: ColorsManager.greyColor, width: 2.0),
|
|
||||||
),
|
|
||||||
height: 70,
|
|
||||||
width: 70,
|
|
||||||
child: Icon(
|
|
||||||
Icons.add,
|
|
||||||
color: ColorsManager.dialogBlueTitle,
|
|
||||||
size: 40,
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
),
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
width: 150,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
BlocProvider.of<SwitchTabsBloc>(context).add(
|
||||||
|
const CreateNewRoutineViewEvent(true),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Card(
|
||||||
|
elevation: 3,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
color: ColorsManager.whiteColors,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.graysColor,
|
||||||
|
borderRadius: BorderRadius.circular(120),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
width: 2.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
height: 70,
|
||||||
|
width: 70,
|
||||||
|
child: Icon(
|
||||||
|
Icons.add,
|
||||||
|
color: ColorsManager.dialogBlueTitle,
|
||||||
|
size: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const Spacer(),
|
);
|
||||||
],
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/routiens/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:syncrow_web/pages/routiens/widgets/routine_devices.dart';
|
import 'package:syncrow_web/pages/routiens/widgets/routine_devices.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart';
|
import 'package:syncrow_web/pages/routiens/widgets/routines_title_widget.dart';
|
||||||
@ -11,87 +13,107 @@ class ConditionsRoutinesDevicesView extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return BlocBuilder<RoutineBloc, RoutineState>(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
builder: (context, state) {
|
||||||
child: SingleChildScrollView(
|
return const Padding(
|
||||||
child: Column(
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: SingleChildScrollView(
|
||||||
children: [
|
child: Column(
|
||||||
const ConditionTitleAndSearchBar(),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
const Wrap(
|
|
||||||
spacing: 10,
|
|
||||||
runSpacing: 10,
|
|
||||||
children: [
|
children: [
|
||||||
DraggableCard(
|
ConditionTitleAndSearchBar(),
|
||||||
imagePath: Assets.tabToRun,
|
SizedBox(height: 10),
|
||||||
title: 'Tab to run',
|
Wrap(
|
||||||
|
spacing: 10,
|
||||||
|
runSpacing: 10,
|
||||||
|
children: [
|
||||||
|
DraggableCard(
|
||||||
|
imagePath: Assets.tabToRun,
|
||||||
|
title: 'Tab to run',
|
||||||
|
deviceData: {
|
||||||
|
'deviceId': 'tab_to_run',
|
||||||
|
'type': 'trigger',
|
||||||
|
'name': 'Tab to run',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DraggableCard(
|
||||||
|
imagePath: Assets.map,
|
||||||
|
title: 'Location',
|
||||||
|
deviceData: {
|
||||||
|
'deviceId': 'location',
|
||||||
|
'type': 'trigger',
|
||||||
|
'name': 'Location',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DraggableCard(
|
||||||
|
imagePath: Assets.weather,
|
||||||
|
title: 'Weather',
|
||||||
|
deviceData: {
|
||||||
|
'deviceId': 'weather',
|
||||||
|
'type': 'trigger',
|
||||||
|
'name': 'Weather',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DraggableCard(
|
||||||
|
imagePath: Assets.schedule,
|
||||||
|
title: 'Schedule',
|
||||||
|
deviceData: {
|
||||||
|
'deviceId': 'schedule',
|
||||||
|
'type': 'trigger',
|
||||||
|
'name': 'Schedule',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
DraggableCard(
|
const SizedBox(height: 10),
|
||||||
imagePath: Assets.map,
|
const TitleRoutine(
|
||||||
title: 'Location',
|
title: 'Conditions',
|
||||||
|
subtitle: '(THEN)',
|
||||||
),
|
),
|
||||||
DraggableCard(
|
const SizedBox(height: 10),
|
||||||
imagePath: Assets.weather,
|
const Wrap(
|
||||||
title: 'Weather',
|
spacing: 10,
|
||||||
|
runSpacing: 10,
|
||||||
|
children: [
|
||||||
|
DraggableCard(
|
||||||
|
imagePath: Assets.notification,
|
||||||
|
title: 'Send Notification',
|
||||||
|
deviceData: {
|
||||||
|
'deviceId': 'notification',
|
||||||
|
'type': 'action',
|
||||||
|
'name': 'Send Notification',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DraggableCard(
|
||||||
|
imagePath: Assets.delay,
|
||||||
|
title: 'Delay the action',
|
||||||
|
deviceData: {
|
||||||
|
'deviceId': 'delay',
|
||||||
|
'type': 'action',
|
||||||
|
'name': 'Delay the action',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
DraggableCard(
|
const SizedBox(height: 10),
|
||||||
imagePath: Assets.schedule,
|
const TitleRoutine(
|
||||||
title: 'Schedule',
|
title: 'Routines',
|
||||||
|
subtitle: '(THEN)',
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const ScenesAndAutomations(),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const TitleRoutine(
|
||||||
|
title: 'Devices',
|
||||||
|
subtitle: '',
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const RoutineDevices(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(
|
),
|
||||||
height: 10,
|
);
|
||||||
),
|
},
|
||||||
const TitleRoutine(
|
|
||||||
title: 'Conditions',
|
|
||||||
subtitle: '(THEN)',
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
const Wrap(
|
|
||||||
spacing: 10,
|
|
||||||
runSpacing: 10,
|
|
||||||
children: [
|
|
||||||
DraggableCard(
|
|
||||||
imagePath: Assets.notification,
|
|
||||||
title: 'Send Notification',
|
|
||||||
),
|
|
||||||
DraggableCard(
|
|
||||||
imagePath: Assets.delay,
|
|
||||||
title: 'Delay the action',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
const TitleRoutine(
|
|
||||||
title: 'Routines',
|
|
||||||
subtitle: '(THEN)',
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
const ScenesAndAutomations(),
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
const TitleRoutine(
|
|
||||||
title: 'Devices',
|
|
||||||
subtitle: '',
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
const RoutineDevices(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
76
lib/pages/routiens/widgets/dialog_footer.dart
Normal file
76
lib/pages/routiens/widgets/dialog_footer.dart
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class DialogFooter extends StatelessWidget {
|
||||||
|
final VoidCallback onCancel;
|
||||||
|
final VoidCallback? onConfirm;
|
||||||
|
final bool isConfirmEnabled;
|
||||||
|
|
||||||
|
const DialogFooter({
|
||||||
|
Key? key,
|
||||||
|
required this.onCancel,
|
||||||
|
required this.onConfirm,
|
||||||
|
required this.isConfirmEnabled,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
_buildFooterButton(
|
||||||
|
context,
|
||||||
|
'Cancel',
|
||||||
|
onCancel,
|
||||||
|
width: isConfirmEnabled ? 299 : 179,
|
||||||
|
),
|
||||||
|
if (isConfirmEnabled)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(width: 1, height: 50, color: ColorsManager.greyColor),
|
||||||
|
_buildFooterButton(
|
||||||
|
context,
|
||||||
|
'Confirm',
|
||||||
|
onConfirm,
|
||||||
|
width: 299,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildFooterButton(
|
||||||
|
BuildContext context,
|
||||||
|
String text,
|
||||||
|
VoidCallback? onTap, {
|
||||||
|
required double width,
|
||||||
|
}) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 50,
|
||||||
|
width: width,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
|
color: text == 'Confirm'
|
||||||
|
? ColorsManager.primaryColorWithOpacity
|
||||||
|
: ColorsManager.textGray,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
31
lib/pages/routiens/widgets/dialog_header.dart
Normal file
31
lib/pages/routiens/widgets/dialog_header.dart
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class DialogHeader extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
const DialogHeader(this.title, {super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
|
color: ColorsManager.primaryColorWithOpacity,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 50),
|
||||||
|
child: Container(
|
||||||
|
height: 1,
|
||||||
|
width: double.infinity,
|
||||||
|
color: ColorsManager.greyColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,156 +2,139 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.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/routiens/bloc/routine_bloc.dart';
|
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
class DraggableCard extends StatelessWidget {
|
class DraggableCard extends StatelessWidget {
|
||||||
|
final String imagePath;
|
||||||
|
final String title;
|
||||||
|
final Map<String, dynamic> deviceData;
|
||||||
|
|
||||||
const DraggableCard({
|
const DraggableCard({
|
||||||
super.key,
|
super.key,
|
||||||
required this.imagePath,
|
required this.imagePath,
|
||||||
required this.title,
|
required this.title,
|
||||||
this.titleColor,
|
required this.deviceData,
|
||||||
this.isDragged = false,
|
|
||||||
this.isDisabled = false,
|
|
||||||
this.deviceData,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final String imagePath;
|
|
||||||
final String title;
|
|
||||||
final Color? titleColor;
|
|
||||||
final bool isDragged;
|
|
||||||
final bool isDisabled;
|
|
||||||
final Map<String, dynamic>? deviceData;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget card = Draggable<Map<String, dynamic>>(
|
|
||||||
data: deviceData ??
|
|
||||||
{
|
|
||||||
'key': UniqueKey().toString(),
|
|
||||||
'imagePath': imagePath,
|
|
||||||
'title': title,
|
|
||||||
},
|
|
||||||
feedback: Transform.rotate(
|
|
||||||
angle: -0.1,
|
|
||||||
child: _buildCardContent(context),
|
|
||||||
),
|
|
||||||
childWhenDragging: _buildGreyContainer(),
|
|
||||||
child: _buildCardContent(context),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isDisabled) {
|
|
||||||
card = AbsorbPointer(child: card);
|
|
||||||
}
|
|
||||||
|
|
||||||
return card;
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildCardContent(BuildContext context) {
|
|
||||||
return BlocBuilder<RoutineBloc, RoutineState>(
|
return BlocBuilder<RoutineBloc, RoutineState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
// Filter functions for this device
|
|
||||||
final deviceFunctions = state.selectedFunctions
|
final deviceFunctions = state.selectedFunctions
|
||||||
.where((f) => f.entityId == deviceData?['deviceId'])
|
.where((f) => f.entityId == deviceData['deviceId'])
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
return Card(
|
return Draggable<Map<String, dynamic>>(
|
||||||
color: ColorsManager.whiteColors,
|
data: deviceData,
|
||||||
child: SizedBox(
|
feedback: Transform.rotate(
|
||||||
width: 90,
|
angle: -0.1,
|
||||||
child: Column(
|
child: _buildCardContent(context, deviceFunctions),
|
||||||
mainAxisSize: MainAxisSize.min,
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
childWhenDragging: _buildGreyContainer(),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
child: _buildCardContent(context, deviceFunctions),
|
||||||
children: [
|
);
|
||||||
SizedBox(
|
},
|
||||||
height: 123,
|
);
|
||||||
child: Column(
|
}
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
Widget _buildCardContent(
|
||||||
Container(
|
BuildContext context, List<DeviceFunctionData> deviceFunctions) {
|
||||||
height: 50,
|
return Card(
|
||||||
width: 50,
|
color: ColorsManager.whiteColors,
|
||||||
decoration: BoxDecoration(
|
child: SizedBox(
|
||||||
color: ColorsManager.CircleImageBackground,
|
width: 90,
|
||||||
borderRadius: BorderRadius.circular(90),
|
child: Column(
|
||||||
border: Border.all(
|
mainAxisSize: MainAxisSize.min,
|
||||||
color: ColorsManager.graysColor,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
),
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
),
|
children: [
|
||||||
padding: const EdgeInsets.all(8),
|
SizedBox(
|
||||||
child: imagePath.contains('.svg')
|
height: 123,
|
||||||
? SvgPicture.asset(
|
child: Column(
|
||||||
imagePath,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
)
|
children: [
|
||||||
: Image.network(imagePath),
|
Container(
|
||||||
|
height: 50,
|
||||||
|
width: 50,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.CircleImageBackground,
|
||||||
|
borderRadius: BorderRadius.circular(90),
|
||||||
|
border: Border.all(
|
||||||
|
color: ColorsManager.graysColor,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
),
|
||||||
Padding(
|
padding: const EdgeInsets.all(8),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 3),
|
child: imagePath.contains('.svg')
|
||||||
|
? SvgPicture.asset(
|
||||||
|
imagePath,
|
||||||
|
)
|
||||||
|
: Image.network(imagePath),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 3),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 2,
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (deviceFunctions.isNotEmpty) ...[
|
||||||
|
const Divider(height: 1),
|
||||||
|
ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 4),
|
||||||
|
itemCount: deviceFunctions.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final function = deviceFunctions[index];
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
'${function.operationName}: ${function.valueDescription}',
|
||||||
textAlign: TextAlign.center,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 2,
|
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
color: titleColor ?? ColorsManager.blackColor,
|
fontSize: 9,
|
||||||
fontSize: 12,
|
color: ColorsManager.textGray,
|
||||||
|
height: 1.2,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
context.read<RoutineBloc>().add(
|
||||||
|
RemoveFunction(function),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.all(2),
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 12,
|
||||||
|
color: ColorsManager.textGray,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
if (deviceFunctions.isNotEmpty) ...[
|
),
|
||||||
const Divider(height: 1),
|
],
|
||||||
ListView.builder(
|
],
|
||||||
shrinkWrap: true,
|
),
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
),
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(vertical: 4, horizontal: 4),
|
|
||||||
itemCount: deviceFunctions.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final function = deviceFunctions[index];
|
|
||||||
return Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'${function.operationName}: ${function.valueDescription}',
|
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
|
||||||
fontSize: 9,
|
|
||||||
color: ColorsManager.textGray,
|
|
||||||
height: 1.2,
|
|
||||||
),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
context.read<RoutineBloc>().add(
|
|
||||||
RemoveFunction(function),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const Padding(
|
|
||||||
padding: EdgeInsets.all(2),
|
|
||||||
child: Icon(
|
|
||||||
Icons.close,
|
|
||||||
size: 12,
|
|
||||||
color: ColorsManager.textGray,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ class IfContainer extends StatelessWidget {
|
|||||||
key: Key(item['key']!),
|
key: Key(item['key']!),
|
||||||
imagePath: item['imagePath']!,
|
imagePath: item['imagePath']!,
|
||||||
title: item['title']!,
|
title: item['title']!,
|
||||||
|
deviceData: item,
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/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.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
|
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
|
||||||
|
|
||||||
class RoutineDevices extends StatelessWidget {
|
class RoutineDevices extends StatelessWidget {
|
||||||
const RoutineDevices({
|
const RoutineDevices({super.key});
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// Get the RoutineBloc instance from the parent
|
||||||
|
final routineBloc = context.read<RoutineBloc>();
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => DeviceManagementBloc()
|
create: (context) => DeviceManagementBloc()..add(FetchDevices()),
|
||||||
..add(
|
|
||||||
FetchDevices(),
|
|
||||||
),
|
|
||||||
child: BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
|
child: BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state is DeviceManagementLoaded) {
|
if (state is DeviceManagementLoaded) {
|
||||||
@ -25,24 +24,34 @@ class RoutineDevices extends StatelessWidget {
|
|||||||
device.productType == '2G' ||
|
device.productType == '2G' ||
|
||||||
device.productType == '3G')
|
device.productType == '3G')
|
||||||
.toList();
|
.toList();
|
||||||
return Wrap(
|
|
||||||
spacing: 10,
|
// Provide the RoutineBloc to the child widgets
|
||||||
runSpacing: 10,
|
return BlocProvider.value(
|
||||||
children: deviceList.asMap().entries.map((entry) {
|
value: routineBloc,
|
||||||
final device = entry.value;
|
child: BlocBuilder<RoutineBloc, RoutineState>(
|
||||||
return DraggableCard(
|
builder: (context, routineState) {
|
||||||
imagePath: device.getDefaultIcon(device.productType),
|
return Wrap(
|
||||||
title: device.name ?? '',
|
spacing: 10,
|
||||||
deviceData: {
|
runSpacing: 10,
|
||||||
'key': UniqueKey().toString(),
|
children: deviceList.asMap().entries.map((entry) {
|
||||||
'imagePath': device.getDefaultIcon(device.productType),
|
final device = entry.value;
|
||||||
'title': device.name ?? '',
|
return DraggableCard(
|
||||||
'deviceId': device.uuid,
|
imagePath: device.getDefaultIcon(device.productType),
|
||||||
'productType': device.productType,
|
title: device.name ?? '',
|
||||||
'functions': device.functions,
|
deviceData: {
|
||||||
},
|
'key': UniqueKey().toString(),
|
||||||
);
|
'imagePath':
|
||||||
}).toList(),
|
device.getDefaultIcon(device.productType),
|
||||||
|
'title': device.name ?? '',
|
||||||
|
'deviceId': device.uuid,
|
||||||
|
'productType': device.productType,
|
||||||
|
'functions': device.functions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
@ -4,40 +4,51 @@ import 'package:syncrow_web/pages/routiens/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:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
class ScenesAndAutomations extends StatelessWidget {
|
class ScenesAndAutomations extends StatefulWidget {
|
||||||
const ScenesAndAutomations({
|
const ScenesAndAutomations({
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ScenesAndAutomations> createState() => _ScenesAndAutomationsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScenesAndAutomationsState extends State<ScenesAndAutomations> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
context.read<RoutineBloc>()
|
||||||
|
..add(const LoadScenes(spaceId))
|
||||||
|
..add(const LoadAutomation(spaceId));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocBuilder<RoutineBloc, RoutineState>(
|
||||||
create: (context) => RoutineBloc()
|
builder: (context, state) {
|
||||||
..add(
|
if (state.scenes.isNotEmpty || state.automations.isNotEmpty) {
|
||||||
const LoadScenes(spaceId),
|
var scenes = [...state.scenes, ...state.automations];
|
||||||
)
|
return Wrap(
|
||||||
..add(
|
spacing: 10,
|
||||||
const LoadAutomation(spaceId),
|
runSpacing: 10,
|
||||||
),
|
children: scenes.asMap().entries.map((entry) {
|
||||||
child: BlocBuilder<RoutineBloc, RoutineState>(
|
final scene = entry.value;
|
||||||
builder: (context, state) {
|
return DraggableCard(
|
||||||
if (state.scenes.isNotEmpty || state.automations.isNotEmpty) {
|
imagePath: Assets.logo,
|
||||||
var scenes = [...state.scenes, ...state.automations];
|
title: scene.name,
|
||||||
return Wrap(
|
deviceData: {
|
||||||
spacing: 10,
|
'deviceId': scene.id,
|
||||||
runSpacing: 10,
|
'name': scene.name,
|
||||||
children: scenes.asMap().entries.map((entry) {
|
'status': scene.status,
|
||||||
final scene = entry.value;
|
'type': scene.type,
|
||||||
return DraggableCard(
|
'icon': scene.icon,
|
||||||
imagePath: Assets.logo,
|
},
|
||||||
title: scene.name,
|
);
|
||||||
);
|
}).toList(),
|
||||||
}).toList(),
|
);
|
||||||
);
|
}
|
||||||
}
|
return const Center(child: CircularProgressIndicator());
|
||||||
return const Center(child: CircularProgressIndicator());
|
},
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ class ThenContainer extends StatelessWidget {
|
|||||||
key: Key(item['key']!),
|
key: Key(item['key']!),
|
||||||
imagePath: item['imagePath']!,
|
imagePath: item['imagePath']!,
|
||||||
title: item['title']!,
|
title: item['title']!,
|
||||||
|
deviceData: item,
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
|
Reference in New Issue
Block a user