mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 07:07:19 +00:00
File diff suppressed because it is too large
Load Diff
@ -206,3 +206,5 @@ class FetchDevicesInRoutine extends RoutineEvent {}
|
||||
class ResetRoutineState extends RoutineEvent {}
|
||||
|
||||
class ClearFunctions extends RoutineEvent {}
|
||||
|
||||
class ResetErrorMessage extends RoutineEvent {}
|
||||
|
@ -22,7 +22,7 @@ class RoutineState extends Equatable {
|
||||
final String? automationId;
|
||||
final bool? isUpdate;
|
||||
final List<AllDevicesModel> devices;
|
||||
final String? automationActionExecutor;
|
||||
// final String? automationActionExecutor;
|
||||
final bool routineTab;
|
||||
final bool createRoutineView;
|
||||
|
||||
@ -48,7 +48,7 @@ class RoutineState extends Equatable {
|
||||
this.automationId,
|
||||
this.isUpdate,
|
||||
this.devices = const [],
|
||||
this.automationActionExecutor,
|
||||
// this.automationActionExecutor,
|
||||
this.routineTab = false,
|
||||
this.createRoutineView = false});
|
||||
|
||||
@ -73,7 +73,7 @@ class RoutineState extends Equatable {
|
||||
String? automationId,
|
||||
bool? isUpdate,
|
||||
List<AllDevicesModel>? devices,
|
||||
String? automationActionExecutor,
|
||||
// String? automationActionExecutor,
|
||||
TextEditingController? nameController,
|
||||
bool? routineTab,
|
||||
bool? createRoutineView,
|
||||
@ -88,18 +88,21 @@ class RoutineState extends Equatable {
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
routineName: routineName ?? this.routineName,
|
||||
selectedIcon: selectedIcon ?? this.selectedIcon,
|
||||
loadScenesErrorMessage: loadScenesErrorMessage ?? this.loadScenesErrorMessage,
|
||||
loadAutomationErrorMessage: loadAutomationErrorMessage ?? this.loadAutomationErrorMessage,
|
||||
loadScenesErrorMessage:
|
||||
loadScenesErrorMessage ?? this.loadScenesErrorMessage,
|
||||
loadAutomationErrorMessage:
|
||||
loadAutomationErrorMessage ?? this.loadAutomationErrorMessage,
|
||||
searchText: searchText ?? this.searchText,
|
||||
isTabToRun: isTabToRun ?? this.isTabToRun,
|
||||
isAutomation: isAutomation ?? this.isAutomation,
|
||||
selectedAutomationOperator: selectedAutomationOperator ?? this.selectedAutomationOperator,
|
||||
selectedAutomationOperator:
|
||||
selectedAutomationOperator ?? this.selectedAutomationOperator,
|
||||
effectiveTime: effectiveTime ?? this.effectiveTime,
|
||||
sceneId: sceneId ?? this.sceneId,
|
||||
automationId: automationId ?? this.automationId,
|
||||
isUpdate: isUpdate ?? this.isUpdate,
|
||||
devices: devices ?? this.devices,
|
||||
automationActionExecutor: automationActionExecutor ?? this.automationActionExecutor,
|
||||
// automationActionExecutor: automationActionExecutor ?? this.automationActionExecutor,
|
||||
routineTab: routineTab ?? this.routineTab,
|
||||
createRoutineView: createRoutineView ?? this.createRoutineView);
|
||||
}
|
||||
@ -126,7 +129,7 @@ class RoutineState extends Equatable {
|
||||
automationId,
|
||||
isUpdate,
|
||||
devices,
|
||||
automationActionExecutor,
|
||||
// automationActionExecutor,
|
||||
routineTab,
|
||||
createRoutineView
|
||||
];
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
@ -6,6 +8,7 @@ import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
|
||||
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class SaveRoutineHelper {
|
||||
static Future<void> showSaveRoutineDialog(BuildContext context) async {
|
||||
@ -98,18 +101,29 @@ class SaveRoutineHelper {
|
||||
final functions =
|
||||
state.selectedFunctions[item['uniqueCustomId']] ?? [];
|
||||
return ListTile(
|
||||
leading: SvgPicture.asset(
|
||||
item['imagePath'],
|
||||
width: 22,
|
||||
height: 22,
|
||||
leading: item['type'] == 'tap_to_run'
|
||||
? Image.memory(
|
||||
base64Decode(item['icon']),
|
||||
width: 22,
|
||||
height: 22,
|
||||
)
|
||||
: SvgPicture.asset(
|
||||
item['imagePath'],
|
||||
width: 22,
|
||||
height: 22,
|
||||
),
|
||||
title: Text(
|
||||
item['title'],
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
fontSize: 14,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
title:
|
||||
Text(item['title'], style: const TextStyle(fontSize: 14)),
|
||||
subtitle: Wrap(
|
||||
children: functions
|
||||
.map((f) => Text(
|
||||
'${f.operationName}: ${f.value}, ',
|
||||
style: const TextStyle(
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.grayColor, fontSize: 8),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 3,
|
||||
@ -124,17 +138,17 @@ class SaveRoutineHelper {
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state.errorMessage != null || state.errorMessage!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
state.errorMessage!,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
// if (state.errorMessage != null || state.errorMessage!.isNotEmpty)
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.all(8.0),
|
||||
// child: Text(
|
||||
// state.errorMessage!,
|
||||
// style: const TextStyle(color: Colors.red),
|
||||
// ),
|
||||
// ),
|
||||
DialogFooter(
|
||||
onCancel: () => Navigator.pop(context),
|
||||
onConfirm: () {
|
||||
onConfirm: () async {
|
||||
if (state.isAutomation) {
|
||||
if (state.automationId != null) {
|
||||
context.read<RoutineBloc>().add(const UpdateAutomation());
|
||||
@ -148,10 +162,9 @@ class SaveRoutineHelper {
|
||||
context.read<RoutineBloc>().add(const CreateSceneEvent());
|
||||
}
|
||||
}
|
||||
if (context.read<RoutineBloc>().state.errorMessage == null ||
|
||||
context.read<RoutineBloc>().state.errorMessage!.isEmpty) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
// if (state.errorMessage == null || state.errorMessage!.isEmpty) {
|
||||
Navigator.pop(context);
|
||||
// }
|
||||
},
|
||||
isConfirmEnabled: true,
|
||||
),
|
||||
|
@ -137,11 +137,13 @@ class ConditionExpr {
|
||||
|
||||
class AutomationAction {
|
||||
String entityId;
|
||||
String? actionType;
|
||||
String actionExecutor;
|
||||
ExecutorProperty? executorProperty;
|
||||
|
||||
AutomationAction({
|
||||
required this.entityId,
|
||||
this.actionType,
|
||||
required this.actionExecutor,
|
||||
this.executorProperty,
|
||||
});
|
||||
@ -150,12 +152,15 @@ class AutomationAction {
|
||||
return {
|
||||
'entityId': entityId,
|
||||
'actionExecutor': actionExecutor,
|
||||
'executorProperty': executorProperty?.toMap(),
|
||||
if (executorProperty != null)
|
||||
'executorProperty': executorProperty?.toMap(),
|
||||
'actionType': actionType
|
||||
};
|
||||
}
|
||||
|
||||
factory AutomationAction.fromMap(Map<String, dynamic> map) {
|
||||
return AutomationAction(
|
||||
actionType: map['actionType'],
|
||||
entityId: map['entityId'] ?? '',
|
||||
actionExecutor: map['actionExecutor'] ?? '',
|
||||
executorProperty: map['executorProperty'] != null
|
||||
|
@ -95,10 +95,12 @@ class CreateSceneModel {
|
||||
|
||||
class CreateSceneAction {
|
||||
String entityId;
|
||||
String? actionType;
|
||||
String actionExecutor;
|
||||
CreateSceneExecutorProperty? executorProperty;
|
||||
|
||||
CreateSceneAction({
|
||||
this.actionType,
|
||||
required this.entityId,
|
||||
required this.actionExecutor,
|
||||
required this.executorProperty,
|
||||
@ -110,6 +112,7 @@ class CreateSceneAction {
|
||||
CreateSceneExecutorProperty? executorProperty,
|
||||
}) {
|
||||
return CreateSceneAction(
|
||||
actionType: actionType ?? this.actionType,
|
||||
entityId: entityId ?? this.entityId,
|
||||
actionExecutor: actionExecutor ?? this.actionExecutor,
|
||||
executorProperty: executorProperty ?? this.executorProperty,
|
||||
@ -125,6 +128,7 @@ class CreateSceneAction {
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
"actionType": actionType,
|
||||
'entityId': entityId,
|
||||
'actionExecutor': actionExecutor,
|
||||
};
|
||||
@ -133,6 +137,7 @@ class CreateSceneAction {
|
||||
|
||||
factory CreateSceneAction.fromMap(Map<String, dynamic> map) {
|
||||
return CreateSceneAction(
|
||||
actionType: map['actionType'],
|
||||
entityId: map['entityId'] ?? '',
|
||||
actionExecutor: map['actionExecutor'] ?? '',
|
||||
executorProperty:
|
||||
|
@ -35,6 +35,20 @@ class DraggableCard extends StatelessWidget {
|
||||
final deviceFunctions =
|
||||
state.selectedFunctions[deviceData['uniqueCustomId']] ?? [];
|
||||
|
||||
int index = state.thenItems.indexWhere(
|
||||
(item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']);
|
||||
|
||||
if (index != -1) {
|
||||
return _buildCardContent(context, deviceFunctions, padding: padding);
|
||||
}
|
||||
|
||||
int ifIndex = state.ifItems.indexWhere(
|
||||
(item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']);
|
||||
|
||||
if (ifIndex != -1) {
|
||||
return _buildCardContent(context, deviceFunctions, padding: padding);
|
||||
}
|
||||
|
||||
return Draggable<Map<String, dynamic>>(
|
||||
data: deviceData,
|
||||
feedback: Transform.rotate(
|
||||
@ -79,17 +93,13 @@ class DraggableCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: imagePath.contains('.svg')
|
||||
? SvgPicture.asset(
|
||||
imagePath,
|
||||
child: deviceData['type'] == 'tap_to_run'
|
||||
? Image.memory(
|
||||
base64Decode(deviceData['icon']),
|
||||
)
|
||||
: imagePath.contains('.png')
|
||||
? Image.asset(
|
||||
imagePath,
|
||||
)
|
||||
: Image.memory(
|
||||
base64Decode(imagePath),
|
||||
),
|
||||
: SvgPicture.asset(
|
||||
imagePath,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
|
@ -7,98 +7,106 @@ import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
|
||||
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class AutomationDialog extends StatelessWidget {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<RoutineBloc, RoutineState>(
|
||||
builder: (context, state) {
|
||||
final isEnabled = state.automationActionExecutor == 'rule_enable';
|
||||
State<AutomationDialog> createState() => _AutomationDialogState();
|
||||
}
|
||||
|
||||
return Dialog(
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Container(
|
||||
width: 400,
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
DialogHeader(automationName),
|
||||
const SizedBox(height: 16),
|
||||
ListTile(
|
||||
leading:
|
||||
SvgPicture.asset(Assets.acPower, width: 24, height: 24),
|
||||
title: const Text('Enable'),
|
||||
trailing: Radio<bool>(
|
||||
value: true,
|
||||
groupValue: isEnabled,
|
||||
onChanged: (bool? value) {
|
||||
if (value == true) {
|
||||
context.read<RoutineBloc>().add(
|
||||
const SetAutomationActionExecutor(
|
||||
automationActionExecutor: 'rule_enable',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: SvgPicture.asset(Assets.acPowerOff,
|
||||
width: 24, height: 24),
|
||||
title: const Text('Disable'),
|
||||
trailing: Radio<bool>(
|
||||
value: false,
|
||||
groupValue: isEnabled,
|
||||
onChanged: (bool? value) {
|
||||
if (value == false) {
|
||||
context.read<RoutineBloc>().add(
|
||||
const SetAutomationActionExecutor(
|
||||
automationActionExecutor: 'rule_disable',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DialogFooter(
|
||||
onConfirm: () {
|
||||
context.read<RoutineBloc>().add(
|
||||
AddFunctionToRoutine(
|
||||
[
|
||||
DeviceFunctionData(
|
||||
entityId: automationId,
|
||||
functionCode: 'automation',
|
||||
value: state.automationActionExecutor,
|
||||
operationName: 'Automation',
|
||||
),
|
||||
],
|
||||
uniqueCustomId,
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
onCancel: () => Navigator.of(context).pop(false),
|
||||
isConfirmEnabled: true,
|
||||
dialogWidth: 400,
|
||||
),
|
||||
],
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,13 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
|
||||
return Wrap(
|
||||
runSpacing: 16,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
state.errorMessage ?? '',
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
@ -214,6 +221,7 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
|
||||
}
|
||||
// final result =
|
||||
// await
|
||||
BlocProvider.of<RoutineBloc>(context).add(ResetErrorMessage());
|
||||
SaveRoutineHelper.showSaveRoutineDialog(context);
|
||||
// if (result != null && result) {
|
||||
// BlocProvider.of<RoutineBloc>(context).add(
|
||||
@ -341,6 +349,7 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
|
||||
}
|
||||
// final result =
|
||||
// await
|
||||
BlocProvider.of<RoutineBloc>(context).add(ResetErrorMessage());
|
||||
SaveRoutineHelper.showSaveRoutineDialog(context);
|
||||
// if (result != null && result) {
|
||||
// BlocProvider.of<RoutineBloc>(context).add(
|
||||
|
@ -87,8 +87,11 @@ class ThenContainer extends StatelessWidget {
|
||||
...state.thenItems[index],
|
||||
'imagePath':
|
||||
Assets.automation,
|
||||
'title': state
|
||||
.thenItems[index]['name'],
|
||||
'title':
|
||||
state.thenItems[index]
|
||||
['name'] ??
|
||||
state.thenItems[index]
|
||||
['title'],
|
||||
}));
|
||||
}
|
||||
return;
|
||||
@ -149,6 +152,12 @@ class ThenContainer extends StatelessWidget {
|
||||
}
|
||||
|
||||
if (mutableData['type'] == 'automation') {
|
||||
int index = state.thenItems.indexWhere(
|
||||
(item) => item['deviceId'] == mutableData['deviceId']);
|
||||
if (index != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AutomationDialog(
|
||||
@ -169,9 +178,14 @@ class ThenContainer extends StatelessWidget {
|
||||
}
|
||||
|
||||
if (mutableData['type'] == 'tap_to_run' && state.isAutomation) {
|
||||
int index = state.thenItems.indexWhere(
|
||||
(item) => item['deviceId'] == mutableData['deviceId']);
|
||||
if (index != -1) {
|
||||
return;
|
||||
}
|
||||
context.read<RoutineBloc>().add(AddToThenContainer({
|
||||
...mutableData,
|
||||
'imagePath': Assets.logo,
|
||||
'imagePath': mutableData['imagePath'] ?? Assets.logo,
|
||||
'title': mutableData['name'],
|
||||
}));
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/navigation_service.dart';
|
||||
|
||||
class CustomSnackBar {
|
||||
@ -11,6 +12,35 @@ class CustomSnackBar {
|
||||
}
|
||||
}
|
||||
|
||||
static redSnackBar(String message) {
|
||||
final key = NavigationService.snackbarKey;
|
||||
BuildContext? currentContext = key?.currentContext;
|
||||
if (key != null && currentContext != null) {
|
||||
final snackBar = SnackBar(
|
||||
padding: const EdgeInsets.all(16),
|
||||
backgroundColor: ColorsManager.red,
|
||||
content: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
const Icon(
|
||||
Icons.check_circle,
|
||||
color: ColorsManager.whiteColors,
|
||||
size: 32,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Text(
|
||||
message,
|
||||
style: Theme.of(currentContext)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green),
|
||||
)
|
||||
]),
|
||||
);
|
||||
key.currentState?.showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
|
||||
static greenSnackBar(String message) {
|
||||
final key = NavigationService.snackbarKey;
|
||||
BuildContext? currentContext = key?.currentContext;
|
||||
@ -29,8 +59,10 @@ class CustomSnackBar {
|
||||
),
|
||||
Text(
|
||||
message,
|
||||
style: Theme.of(currentContext).textTheme.bodySmall!.copyWith(
|
||||
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green),
|
||||
style: Theme.of(currentContext)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green),
|
||||
)
|
||||
]),
|
||||
);
|
||||
|
Reference in New Issue
Block a user