Compare commits

..
Author SHA1 Message Date
ashraf_personal d9ef5b4574 push fixes and merge 2024-12-03 01:02:13 +03:00
ashraf_personal c58f2eb24e fixed autoamtion update 2024-12-03 00:29:59 +03:00
Abdullah Alassaf 4e94d2df89 Bug fixes 2024-12-03 00:02:52 +03:00
mohammad 2f5c5d7da1 rule_disable selected & CreateAutomation actionType 2024-12-02 16:49:44 +03:00
Abdullah Alassaf ff4ce8ed01 Bug fixes 2024-12-02 03:58:31 +03:00
mohammad 1aa6b78fb4 Bug fixes 2024-12-01 12:05:19 +03:00
Abdullah e003d9b8b3 Merge pull request #56 from SyncrowIOT/routines_fixes
Routines fixes
2024-12-01 10:50:54 +03:00
20 changed files with 793 additions and 684 deletions
@@ -1,28 +0,0 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
part 'switch_tabs_event.dart';
part 'switch_tabs_state.dart';
class SwitchTabsBloc extends Bloc<SwitchTabsEvent, SwitchTabsState> {
SwitchTabsBloc() : super(SwitchTabsInitial()) {
on<TriggerSwitchTabsEvent>(_switchTab);
on<CreateNewRoutineViewEvent>(_newRoutineView);
}
FutureOr<void> _switchTab(
TriggerSwitchTabsEvent event,
Emitter<SwitchTabsState> emit,
) {
emit(SelectedTabState(event.isRoutineView));
}
FutureOr<void> _newRoutineView(
CreateNewRoutineViewEvent event,
Emitter<SwitchTabsState> emit,
) {
emit(ShowCreateRoutineState(event.showCreateNewRoutineView));
}
}
@@ -1,21 +0,0 @@
part of 'switch_tabs_bloc.dart';
sealed class SwitchTabsEvent extends Equatable {
const SwitchTabsEvent();
}
class TriggerSwitchTabsEvent extends SwitchTabsEvent {
final bool isRoutineView;
const TriggerSwitchTabsEvent(this.isRoutineView);
@override
List<Object?> get props => [isRoutineView];
}
class CreateNewRoutineViewEvent extends SwitchTabsEvent {
final bool showCreateNewRoutineView;
const CreateNewRoutineViewEvent(this.showCreateNewRoutineView);
@override
List<Object?> get props => [showCreateNewRoutineView];
}
@@ -1,26 +0,0 @@
part of 'switch_tabs_bloc.dart';
sealed class SwitchTabsState extends Equatable {
const SwitchTabsState();
}
final class SwitchTabsInitial extends SwitchTabsState {
@override
List<Object> get props => [];
}
class SelectedTabState extends SwitchTabsState {
final bool selectedTab;
const SelectedTabState(this.selectedTab);
@override
List<Object?> get props => [selectedTab];
}
class ShowCreateRoutineState extends SwitchTabsState {
final bool showCreateRoutine;
const ShowCreateRoutineState(this.showCreateRoutine);
@override
List<Object?> get props => [showCreateRoutine];
}
@@ -1,9 +1,9 @@
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/device_managment/all_devices/bloc/switch_tabs/switch_tabs_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_managment_body.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart'; import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routiens/view/routines_view.dart'; import 'package:syncrow_web/pages/routiens/view/routines_view.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@@ -18,10 +18,6 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider(
create: (context) =>
SwitchTabsBloc()..add(const TriggerSwitchTabsEvent(false)),
),
BlocProvider( BlocProvider(
create: (context) => DeviceManagementBloc()..add(FetchDevices()), create: (context) => DeviceManagementBloc()..add(FetchDevices()),
), ),
@@ -33,8 +29,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
style: Theme.of(context).textTheme.headlineLarge, style: Theme.of(context).textTheme.headlineLarge,
), ),
), ),
centerBody: BlocBuilder<SwitchTabsBloc, SwitchTabsState>( centerBody: BlocBuilder<RoutineBloc, RoutineState>(builder: (context, state) {
builder: (context, state) {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -44,20 +39,14 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
), ),
onPressed: () { onPressed: () {
context context
.read<SwitchTabsBloc>() .read<RoutineBloc>()
.add(const TriggerSwitchTabsEvent(false)); .add(const TriggerSwitchTabsEvent(isRoutineTab: false));
}, },
child: Text( child: Text(
'Devices', 'Devices',
style: context.textTheme.titleMedium?.copyWith( style: context.textTheme.titleMedium?.copyWith(
color: color: !state.routineTab ? ColorsManager.whiteColors : ColorsManager.grayColor,
state is SelectedTabState && state.selectedTab == false fontWeight: !state.routineTab ? FontWeight.w700 : FontWeight.w400,
? ColorsManager.whiteColors
: ColorsManager.grayColor,
fontWeight: (state is SelectedTabState) &&
state.selectedTab == false
? FontWeight.w700
: FontWeight.w400,
), ),
), ),
), ),
@@ -66,21 +55,13 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
backgroundColor: null, backgroundColor: null,
), ),
onPressed: () { onPressed: () {
context context.read<RoutineBloc>().add(const TriggerSwitchTabsEvent(isRoutineTab: true));
.read<SwitchTabsBloc>()
.add(const TriggerSwitchTabsEvent(true));
}, },
child: Text( child: Text(
'Routines', 'Routines',
style: context.textTheme.titleMedium?.copyWith( style: context.textTheme.titleMedium?.copyWith(
color: color: state.routineTab ? ColorsManager.whiteColors : ColorsManager.grayColor,
(state is SelectedTabState) && state.selectedTab == true fontWeight: state.routineTab ? FontWeight.w700 : FontWeight.w400,
? ColorsManager.whiteColors
: ColorsManager.grayColor,
fontWeight:
(state is SelectedTabState) && state.selectedTab == true
? FontWeight.w700
: FontWeight.w400,
), ),
), ),
), ),
@@ -88,13 +69,12 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
); );
}), }),
rightBody: const NavigateHomeGridView(), rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocBuilder<SwitchTabsBloc, SwitchTabsState>( scaffoldBody: BlocBuilder<RoutineBloc, RoutineState>(builder: (context, state) {
builder: (context, state) { if (state.routineTab) {
if (state is SelectedTabState && state.selectedTab) {
return const RoutinesView(); return const RoutinesView();
} }
if (state is ShowCreateRoutineState && state.showCreateRoutine) { if (state.createRoutineView) {
return CreateNewRoutineView(); return CreateNewRoutineView();
} }
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>( return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
@@ -104,8 +84,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
} else if (deviceState is DeviceManagementLoaded) { } else if (deviceState is DeviceManagementLoaded) {
return DeviceManagementBody(devices: deviceState.devices); return DeviceManagementBody(devices: deviceState.devices);
} else if (deviceState is DeviceManagementFiltered) { } else if (deviceState is DeviceManagementFiltered) {
return DeviceManagementBody( return DeviceManagementBody(devices: deviceState.filteredDevices);
devices: deviceState.filteredDevices);
} else { } else {
return const Center(child: Text('Error fetching Devices')); return const Center(child: Text('Error fetching Devices'));
} }
+4 -2
View File
@@ -6,6 +6,7 @@ import 'package:syncrow_web/pages/auth/model/user_model.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/home/bloc/home_state.dart'; import 'package:syncrow_web/pages/home/bloc/home_state.dart';
import 'package:syncrow_web/pages/home/home_model/home_item_model.dart'; import 'package:syncrow_web/pages/home/home_model/home_item_model.dart';
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/services/home_api.dart'; import 'package:syncrow_web/services/home_api.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
@@ -41,8 +42,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async { Future _fetchUserInfo(FetchUserInfo event, Emitter<HomeState> emit) async {
try { try {
var uuid = var uuid = await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
await const FlutterSecureStorage().read(key: UserModel.userUuidKey);
user = await HomeApi().fetchUserInfo(uuid); user = await HomeApi().fetchUserInfo(uuid);
emit(HomeInitial()); emit(HomeInitial());
} catch (e) { } catch (e) {
@@ -84,6 +84,8 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
icon: Assets.devicesIcon, icon: Assets.devicesIcon,
active: true, active: true,
onPress: (context) { onPress: (context) {
BlocProvider.of<RoutineBloc>(context)
.add(const TriggerSwitchTabsEvent(isRoutineTab: false));
context.go(RoutesConst.deviceManagementPage); context.go(RoutesConst.deviceManagementPage);
}, },
color: ColorsManager.primaryColor, color: ColorsManager.primaryColor,
@@ -13,7 +13,7 @@ import 'package:syncrow_web/pages/routiens/models/routine_model.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/services/routines_api.dart'; import 'package:syncrow_web/services/routines_api.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/navigation_service.dart'; import 'package:syncrow_web/utils/snack_bar.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
part 'routine_event.dart'; part 'routine_event.dart';
@@ -45,7 +45,36 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
on<FetchDevicesInRoutine>(_fetchDevices); on<FetchDevicesInRoutine>(_fetchDevices);
on<UpdateScene>(_onUpdateScene); on<UpdateScene>(_onUpdateScene);
on<UpdateAutomation>(_onUpdateAutomation); on<UpdateAutomation>(_onUpdateAutomation);
on<SetAutomationActionExecutor>(_onSetAutomationActionExecutor); on<TriggerSwitchTabsEvent>(_triggerSwitchTabsEvent);
on<CreateNewRoutineViewEvent>(_createNewRoutineViewEvent);
on<ResetErrorMessage>(_resetErrorMessage);
}
FutureOr<void> _triggerSwitchTabsEvent(
TriggerSwitchTabsEvent event,
Emitter<RoutineState> emit,
) {
emit(state.copyWith(
routineTab: event.isRoutineTab, createRoutineView: false));
add(ResetRoutineState());
if (event.isRoutineTab) {
add(const LoadScenes(spaceId, communityId));
add(const LoadAutomation(spaceId));
}
}
_resetErrorMessage(
ResetErrorMessage event,
Emitter<RoutineState> emit,
) {
emit(state.copyWith(errorMessage: ''));
}
FutureOr<void> _createNewRoutineViewEvent(
CreateNewRoutineViewEvent event,
Emitter<RoutineState> emit,
) {
emit(state.copyWith(createRoutineView: event.createRoutineView));
} }
void _onAddToIfContainer(AddToIfContainer event, Emitter<RoutineState> emit) { void _onAddToIfContainer(AddToIfContainer event, Emitter<RoutineState> emit) {
@@ -196,10 +225,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(selectedIcon: event.icon)); emit(state.copyWith(selectedIcon: event.icon));
} }
bool _isFirstActionDelay(List<Map<String, dynamic>> actions) { // bool _isFirstActionDelay(List<Map<String, dynamic>> actions) {
if (actions.isEmpty) return false; // if (actions.isEmpty) return false;
return actions.first['deviceId'] == 'delay'; // return actions.first['deviceId'] == 'delay';
} // }
bool _isLastActionDelay(List<Map<String, dynamic>> actions) { bool _isLastActionDelay(List<Map<String, dynamic>> actions) {
if (actions.isEmpty) return false; if (actions.isEmpty) return false;
@@ -210,17 +239,18 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
CreateSceneEvent event, Emitter<RoutineState> emit) async { CreateSceneEvent event, Emitter<RoutineState> emit) async {
try { try {
// Check if first action is delay // Check if first action is delay
if (_isFirstActionDelay(state.thenItems)) { // if (_isFirstActionDelay(state.thenItems)) {
emit(state.copyWith( // emit(state.copyWith(
errorMessage: 'Cannot have delay as the first action', // errorMessage: 'Cannot have delay as the first action',
isLoading: false, // isLoading: false,
)); // ));
return; // return;
} // }
if (_isLastActionDelay(state.thenItems)) { if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith( emit(state.copyWith(
errorMessage: 'Cannot have delay as the last action', errorMessage:
'A delay condition cannot be the only or the last action',
isLoading: false, isLoading: false,
)); ));
return; return;
@@ -233,6 +263,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return functions.map((function) { return functions.map((function) {
if (function.functionCode == 'automation') { if (function.functionCode == 'automation') {
return CreateSceneAction( return CreateSceneAction(
actionType: 'automation',
entityId: function.entityId, entityId: function.entityId,
actionExecutor: function.value, actionExecutor: function.value,
executorProperty: null, executorProperty: null,
@@ -252,6 +283,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
return CreateSceneAction( return CreateSceneAction(
actionType: 'scene',
entityId: function.entityId, entityId: function.entityId,
actionExecutor: 'device_issue', actionExecutor: 'device_issue',
executorProperty: CreateSceneExecutorProperty( executorProperty: CreateSceneExecutorProperty(
@@ -267,21 +299,20 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
spaceUuid: spaceId, spaceUuid: spaceId,
iconId: state.selectedIcon ?? '', iconId: state.selectedIcon ?? '',
showInDevice: true, showInDevice: true,
sceneName: state.routineName!, sceneName: state.routineName ?? '',
decisionExpr: 'and', decisionExpr: 'and',
actions: actions, actions: actions,
); );
final result = await SceneApi.createScene(createSceneModel); final result = await SceneApi.createScene(createSceneModel);
if (result['success']) { if (result['success']) {
Navigator.of(NavigationService.navigatorKey.currentContext!).pop();
add(ResetRoutineState()); add(ResetRoutineState());
add(const LoadScenes(spaceId, communityId)); add(const LoadScenes(spaceId, communityId));
add(const LoadAutomation(spaceId)); add(const LoadAutomation(spaceId));
} else { } else {
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
errorMessage: result['message'], errorMessage: 'Something went wrong',
)); ));
} }
} catch (e) { } catch (e) {
@@ -299,21 +330,26 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith( emit(state.copyWith(
errorMessage: 'Automation name is required', errorMessage: 'Automation name is required',
)); ));
CustomSnackBar.redSnackBar('Automation name is required');
return; return;
} }
if (_isFirstActionDelay(state.thenItems)) { // if (_isFirstActionDelay(state.thenItems)) {
emit(state.copyWith( // emit(state.copyWith(
errorMessage: 'Cannot have delay as the first action', // errorMessage: 'Cannot have delay as the first action',
isLoading: false, // isLoading: false,
)); // ));
return; // CustomSnackBar.redSnackBar('Cannot have delay as the first action');
}
// return;
// }
if (_isLastActionDelay(state.thenItems)) { if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith( emit(state.copyWith(
errorMessage: 'Cannot have delay as the last action', errorMessage:
'A delay condition cannot be the only or the last action',
isLoading: false, isLoading: false,
)); ));
CustomSnackBar.redSnackBar('Cannot have delay as the last action');
return; return;
} }
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
@@ -346,12 +382,25 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
final actions = state.thenItems.expand((item) { final actions = state.thenItems.expand((item) {
if (item['type'] == 'tap_to_run') {
return [
AutomationAction(
actionType: 'scene',
entityId: item['deviceId'],
actionExecutor: 'rule_trigger',
),
];
}
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
return functions.map((function) { return functions.map((function) {
if (function.functionCode == 'automation') { if (function.functionCode == 'automation') {
return AutomationAction( return AutomationAction(
actionType: 'automation',
entityId: function.entityId, entityId: function.entityId,
actionExecutor: function.value, actionExecutor: function.value,
executorProperty: null,
); );
} }
@@ -378,7 +427,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final createAutomationModel = CreateAutomationModel( final createAutomationModel = CreateAutomationModel(
spaceUuid: spaceId, spaceUuid: spaceId,
automationName: state.routineName!, automationName: state.routineName ?? '',
decisionExpr: state.selectedAutomationOperator, decisionExpr: state.selectedAutomationOperator,
effectiveTime: EffectiveTime( effectiveTime: EffectiveTime(
start: state.effectiveTime?.start ?? '00:00', start: state.effectiveTime?.start ?? '00:00',
@@ -391,7 +440,6 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final result = await SceneApi.createAutomation(createAutomationModel); final result = await SceneApi.createAutomation(createAutomationModel);
if (result['success']) { if (result['success']) {
Navigator.of(NavigationService.navigatorKey.currentContext!).pop();
add(ResetRoutineState()); add(ResetRoutineState());
add(const LoadAutomation(spaceId)); add(const LoadAutomation(spaceId));
add(const LoadScenes(spaceId, communityId)); add(const LoadScenes(spaceId, communityId));
@@ -400,12 +448,14 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
isLoading: false, isLoading: false,
errorMessage: result['message'], errorMessage: result['message'],
)); ));
CustomSnackBar.redSnackBar('Something went wrong');
} }
} catch (e) { } catch (e) {
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
errorMessage: 'Something went wrong', errorMessage: 'Something went wrong',
)); ));
CustomSnackBar.redSnackBar('Something went wrong');
} }
} }
@@ -454,7 +504,9 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
FutureOr<void> _onSetRoutineName( FutureOr<void> _onSetRoutineName(
SetRoutineName event, Emitter<RoutineState> emit) { SetRoutineName event, Emitter<RoutineState> emit) {
emit(state.copyWith(routineName: event.name)); emit(state.copyWith(
routineName: event.name,
));
} }
( (
@@ -591,7 +643,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
selectedFunctions: {}, selectedFunctions: {},
ifItems: [], ifItems: [],
thenItems: [], thenItems: [],
errorMessage: null, errorMessage: '',
loadScenesErrorMessage: null, loadScenesErrorMessage: null,
loadAutomationErrorMessage: null, loadAutomationErrorMessage: null,
searchText: '', searchText: '',
@@ -666,7 +718,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
operationName: 'Automation', operationName: 'Automation',
), ),
); );
emit(state.copyWith(automationActionExecutor: action.actionExecutor)); // emit(state.copyWith(automationActionExecutor: action.actionExecutor));
} else if (action.executorProperty != null && } else if (action.executorProperty != null &&
action.actionExecutor != 'delay') { action.actionExecutor != 'delay') {
if (!updatedFunctions.containsKey(uniqueCustomId)) { if (!updatedFunctions.containsKey(uniqueCustomId)) {
@@ -740,180 +792,27 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
Future<void> _onGetAutomationDetails(
GetAutomationDetails event, Emitter<RoutineState> emit) async {
try {
emit(state.copyWith(
isLoading: true,
isUpdate: true,
isTabToRun: false,
automationId: event.automationId,
isAutomation: true,
ifItems: [],
thenItems: [],
));
final automationDetails =
await SceneApi.getAutomationDetails(event.automationId);
final List<Map<String, dynamic>> thenItems;
final List<Map<String, dynamic>> ifItems;
final Map<String, List<DeviceFunctionData>> updatedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
ifItems = automationDetails.conditions?.map((condition) {
late AllDevicesModel? matchingDevice;
for (var device in state.devices) {
if (device.uuid == condition.entityId) {
matchingDevice = device;
break;
}
}
final cardData = {
'entityId': condition.entityId,
'uniqueCustomId': const Uuid().v4(),
'deviceId': condition.expr.statusCode == 'delay'
? 'delay'
: condition.entityId,
'title': condition.expr.statusCode == 'delay'
? 'Delay'
: (matchingDevice?.name ?? 'Device'),
'productType': condition.productType,
'functions': matchingDevice?.functions ?? [],
'imagePath':
matchingDevice?.getDefaultIcon(condition.productType) ?? '',
'device': matchingDevice,
};
final functions = matchingDevice?.functions ?? [];
for (var function in functions) {
if (function.code == condition.expr.statusCode) {
updatedFunctions[cardData['uniqueCustomId'].toString()] = [
DeviceFunctionData(
entityId: condition.entityId,
functionCode: condition.expr.statusCode,
value: condition.expr.statusValue,
operationName: function.operationName,
),
];
break;
}
}
return cardData;
}).toList() ??
[];
// Create then items from actions
thenItems = automationDetails.actions.map((action) {
final matchingDevice = state.devices.firstWhere(
(device) => device.uuid == action.entityId,
orElse: () => AllDevicesModel(
uuid: action.entityId,
name: action.entityId,
productType: action.productType,
),
);
final cardData = {
'entityId': action.entityId,
'uniqueCustomId': const Uuid().v4(),
'deviceId':
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'title': action.actionExecutor == 'delay'
? 'Delay'
: (matchingDevice.name ?? 'Device'),
'productType': action.productType,
'functions': matchingDevice.functions,
'imagePath': matchingDevice.getDefaultIcon(action.productType),
'device': matchingDevice,
};
if (action.executorProperty != null &&
action.actionExecutor != 'delay') {
final functions = matchingDevice.functions;
final functionCode = action.executorProperty!.functionCode;
for (var function in functions) {
if (function.code == functionCode) {
updatedFunctions[cardData['uniqueCustomId'].toString()] = [
DeviceFunctionData(
entityId: action.entityId,
functionCode: functionCode ?? '',
value: action.executorProperty!.functionValue,
operationName: function.operationName,
),
];
break;
}
}
} else if (action.actionExecutor == 'delay') {
final delayFunction = DelayFunction(
deviceId: action.entityId,
deviceName: 'Delay',
);
updatedFunctions[cardData['uniqueCustomId'].toString()] = [
DeviceFunctionData(
entityId: action.entityId,
functionCode: 'delay',
value: action.executorProperty?.delaySeconds ?? 0,
operationName: delayFunction.operationName,
),
];
} else if (action.actionExecutor == 'rule_disable' ||
action.actionExecutor == 'rule_enable') {
updatedFunctions[cardData['uniqueCustomId'].toString()] = [
DeviceFunctionData(
entityId: action.entityId,
functionCode: 'automation',
value: action.actionExecutor,
operationName: action.name ?? 'Automation',
),
];
}
return cardData;
}).toList();
emit(state.copyWith(
isLoading: false,
routineName: automationDetails.name,
selectedAutomationOperator: automationDetails.decisionExpr,
effectiveTime: automationDetails.effectiveTime,
isAutomation: true,
thenItems: thenItems,
ifItems: ifItems,
selectedFunctions: updatedFunctions,
automationId: automationDetails.automationId,
));
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'Failed to load automation details: $e',
));
}
}
FutureOr<void> _onResetRoutineState( FutureOr<void> _onResetRoutineState(
ResetRoutineState event, Emitter<RoutineState> emit) { ResetRoutineState event, Emitter<RoutineState> emit) {
emit(state.copyWith( emit(state.copyWith(
ifItems: [], ifItems: [],
thenItems: [], thenItems: [],
selectedFunctions: {}, selectedFunctions: {},
scenes: [], scenes: [],
automations: [], automations: [],
isLoading: false, isLoading: false,
errorMessage: null, errorMessage: '',
loadScenesErrorMessage: null, loadScenesErrorMessage: null,
loadAutomationErrorMessage: null, loadAutomationErrorMessage: null,
searchText: '', searchText: '',
selectedIcon: null, selectedIcon: null,
isTabToRun: false, isTabToRun: false,
isAutomation: false, isAutomation: false,
selectedAutomationOperator: 'or', selectedAutomationOperator: 'or',
effectiveTime: null, effectiveTime: null,
routineName: null, routineName: '',
)); isUpdate: false,
createRoutineView: false));
} }
FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) { FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) {
@@ -929,6 +828,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
add(const LoadScenes(spaceId, communityId)); add(const LoadScenes(spaceId, communityId));
add(const LoadAutomation(spaceId)); add(const LoadAutomation(spaceId));
add(ResetRoutineState()); add(ResetRoutineState());
emit(state.copyWith(isLoading: false, createRoutineView: false));
} catch (e) { } catch (e) {
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
@@ -967,18 +867,19 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
UpdateScene event, Emitter<RoutineState> emit) async { UpdateScene event, Emitter<RoutineState> emit) async {
try { try {
// Check if first action is delay // Check if first action is delay
if (_isFirstActionDelay(state.thenItems)) { // if (_isFirstActionDelay(state.thenItems)) {
emit(state.copyWith( // emit(state.copyWith(
errorMessage: 'Cannot have delay as the first action', // errorMessage: 'Cannot have delay as the first action',
isLoading: false, // isLoading: false,
)); // ));
return; // return;
} // }
if (_isLastActionDelay(state.thenItems)) { if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith( emit(state.copyWith(
errorMessage: 'Cannot have delay as the last action', errorMessage:
'A delay condition cannot be the only or the last action',
isLoading: false, isLoading: false,
)); ));
return; return;
@@ -991,6 +892,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return functions.map((function) { return functions.map((function) {
if (function.functionCode == 'automation') { if (function.functionCode == 'automation') {
return CreateSceneAction( return CreateSceneAction(
actionType: 'automation',
entityId: function.entityId, entityId: function.entityId,
actionExecutor: function.value, actionExecutor: function.value,
executorProperty: null, executorProperty: null,
@@ -1033,7 +935,6 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final result = final result =
await SceneApi.updateScene(createSceneModel, state.sceneId ?? ''); await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
if (result['success']) { if (result['success']) {
Navigator.of(NavigationService.navigatorKey.currentContext!).pop();
add(ResetRoutineState()); add(ResetRoutineState());
add(const LoadScenes(spaceId, communityId)); add(const LoadScenes(spaceId, communityId));
add(const LoadAutomation(spaceId)); add(const LoadAutomation(spaceId));
@@ -1053,84 +954,324 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
FutureOr<void> _onUpdateAutomation( FutureOr<void> _onUpdateAutomation(
UpdateAutomation event, Emitter<RoutineState> emit) async { UpdateAutomation event, Emitter<RoutineState> emit) async {
if (_isFirstActionDelay(state.thenItems)) {
emit(state.copyWith(
errorMessage: 'Cannot have delay as the first action',
isLoading: false,
));
return;
}
emit(state.copyWith(isLoading: true));
try { try {
final conditions = state.ifItems if (state.routineName == null || state.routineName!.isEmpty) {
.map((item) { emit(state.copyWith(
final functions = errorMessage: 'Automation name is required',
state.selectedFunctions[item['uniqueCustomId']] ?? []; ));
if (functions.isEmpty) return null; return;
}
// if (_isFirstActionDelay(state.thenItems)) {
// emit(state.copyWith(
// errorMessage: 'Cannot have delay as the first action',
// isLoading: false,
// ));
// return;
// }
final function = functions.first; if (_isLastActionDelay(state.thenItems)) {
return Condition( emit(state.copyWith(
code: state.ifItems.indexOf(item) + 1, errorMessage: 'Cannot have delay as the last action',
isLoading: false,
));
return;
}
emit(state.copyWith(isLoading: true, errorMessage: null));
final conditions = state.ifItems.expand((item) {
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
return functions.map((function) {
return Condition(
code: state.selectedFunctions[item['uniqueCustomId']]!.indexOf(
function,
) +
1,
entityId: function.entityId,
entityType: 'device_report',
expr: ConditionExpr(
statusCode: function.functionCode,
comparator: function.condition ?? '==',
statusValue: function.value,
),
);
});
}).toList();
if (conditions.isEmpty) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'At least one condition is required',
));
return;
}
final actions = state.thenItems.expand((item) {
if (item['type'] == 'tap_to_run' || item['type'] == 'scene') {
return [
AutomationAction(
actionType: 'scene',
entityId: item['deviceId'],
actionExecutor: 'rule_trigger',
),
];
}
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
return functions.map((function) {
if (function.functionCode == 'automation') {
return AutomationAction(
actionType: 'automation',
entityId: function.entityId, entityId: function.entityId,
entityType: item['productType'], actionExecutor: function.value,
expr: ConditionExpr( executorProperty: null,
statusCode: function.functionCode,
statusValue: function.value,
comparator: function.condition ?? '==',
),
); );
}) }
.whereType<Condition>()
.toList();
final actions = state.thenItems if (item['deviceId'] == 'delay') {
.map((item) {
final functions =
state.selectedFunctions[item['uniqueCustomId']] ?? [];
if (functions.isEmpty) return null;
final function = functions.first;
return AutomationAction( return AutomationAction(
entityId: function.entityId, entityId: function.entityId,
actionExecutor: function.actionExecutor, actionType: 'automation',
actionExecutor: 'delay',
executorProperty: ExecutorProperty( executorProperty: ExecutorProperty(
functionCode: function.functionCode, delaySeconds: int.tryParse(function.value.toString()) ?? 0,
functionValue: function.value,
delaySeconds: function.functionCode == 'delay'
? (function.value as num).toInt()
: null,
), ),
); );
}) }
.whereType<AutomationAction>()
.toList(); return AutomationAction(
entityId: function.entityId,
actionExecutor: 'device_issue',
actionType: 'automation',
executorProperty: ExecutorProperty(
functionCode: function.functionCode,
functionValue: function.value,
),
);
});
}).toList();
final createAutomationModel = CreateAutomationModel( final createAutomationModel = CreateAutomationModel(
spaceUuid: spaceId, spaceUuid: spaceId,
automationName: state.routineName ?? '', automationName: state.routineName ?? '',
decisionExpr: state.selectedAutomationOperator, decisionExpr: state.selectedAutomationOperator,
effectiveTime: effectiveTime: EffectiveTime(
state.effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''), start: state.effectiveTime?.start ?? '00:00',
end: state.effectiveTime?.end ?? '23:59',
loops: state.effectiveTime?.loops ?? '1111111',
),
conditions: conditions, conditions: conditions,
actions: actions, actions: actions,
); );
await SceneApi.updateAutomation( final result = await SceneApi.updateAutomation(
createAutomationModel, state.automationId ?? ''); createAutomationModel, state.automationId ?? '');
add(const LoadAutomation(spaceId)); if (result['success']) {
emit(state.copyWith(isLoading: false)); add(ResetRoutineState());
add(const LoadAutomation(spaceId));
add(const LoadScenes(spaceId, communityId));
} else {
emit(state.copyWith(
isLoading: false,
errorMessage: result['message'],
));
}
} catch (e) { } catch (e) {
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
errorMessage: 'Failed to update automation: $e', errorMessage: 'Something went wrong',
)); ));
} }
} }
FutureOr<void> _onSetAutomationActionExecutor( Future<void> _onGetAutomationDetails(
SetAutomationActionExecutor event, Emitter<RoutineState> emit) { GetAutomationDetails event, Emitter<RoutineState> emit) async {
emit(state.copyWith( try {
automationActionExecutor: event.automationActionExecutor)); emit(state.copyWith(
isLoading: true,
isUpdate: true,
isTabToRun: false,
automationId: event.automationId,
isAutomation: true,
ifItems: [],
thenItems: [],
));
final automationDetails =
await SceneApi.getAutomationDetails(event.automationId);
final Map<String, Map<String, dynamic>> deviceCards = {};
final Map<String, List<DeviceFunctionData>> updatedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
for (RoutineCondition condition in automationDetails.conditions ?? []) {
AllDevicesModel? matchingDevice = state.devices.firstWhere(
(device) => device.uuid == condition.entityId,
orElse: () => AllDevicesModel(
uuid: condition.entityId,
name: condition.entityId,
productType: condition.productType,
),
);
final deviceId = condition.entityId;
if (!deviceCards.containsKey(deviceId)) {
deviceCards[deviceId] = {
'entityId': condition.entityId,
'deviceId': condition.entityId,
'uniqueCustomId': const Uuid().v4(),
'title': matchingDevice.name ?? 'Device',
'productType': condition.productType,
'functions': matchingDevice.functions,
'imagePath': matchingDevice.getDefaultIcon(condition.productType),
'device': matchingDevice,
'type': 'condition',
};
}
final cardData = deviceCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString();
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
final functions = matchingDevice.functions;
for (var function in functions) {
if (function.code == condition.expr.statusCode) {
updatedFunctions[uniqueCustomId]!.add(
DeviceFunctionData(
entityId: condition.entityId,
functionCode: condition.expr.statusCode,
value: condition.expr.statusValue,
operationName: function.operationName,
),
);
break;
}
}
}
// Process actions (thenItems)
for (var action in automationDetails.actions) {
AllDevicesModel? matchingDevice = state.devices.firstWhere(
(device) => device.uuid == action.entityId,
orElse: () => AllDevicesModel(
uuid: action.entityId,
name: action.entityId,
productType: action.productType,
),
);
final deviceId = action.actionExecutor == 'delay'
? '${action.entityId}_delay'
: action.entityId;
if (!deviceCards.containsKey(deviceId)) {
deviceCards[deviceId] = {
'entityId': action.entityId,
'deviceId':
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': const Uuid().v4(),
'title': action.actionExecutor == 'delay'
? 'Delay'
: (action.type == 'scene' || action.type == 'automation')
? action.name
: (matchingDevice.name ?? 'Device'),
'productType': action.productType,
'functions': matchingDevice.functions,
'imagePath': action.actionExecutor == 'delay'
? Assets.delay
: action.type == 'automation'
? Assets.automation
: matchingDevice.getDefaultIcon(action.productType),
'device': matchingDevice,
'type': action.type == 'scene'
? 'scene'
: action.type == 'automation'
? 'automation'
: 'action',
};
}
final cardData = deviceCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString();
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
if (action.executorProperty != null &&
action.actionExecutor != 'delay') {
final functions = matchingDevice.functions;
final functionCode = action.executorProperty!.functionCode;
for (var function in functions) {
if (function.code == functionCode) {
updatedFunctions[uniqueCustomId]!.add(
DeviceFunctionData(
entityId: action.entityId,
functionCode: functionCode ?? '',
value: action.executorProperty!.functionValue,
operationName: function.operationName,
),
);
break;
}
}
} else if (action.actionExecutor == 'delay') {
final delayFunction = DelayFunction(
deviceId: action.entityId,
deviceName: 'Delay',
);
updatedFunctions[uniqueCustomId]!.add(
DeviceFunctionData(
entityId: action.entityId,
functionCode: 'delay',
value: action.executorProperty?.delaySeconds ?? 0,
operationName: delayFunction.operationName,
),
);
} else if (action.actionExecutor == 'rule_disable' ||
action.actionExecutor == 'rule_enable') {
updatedFunctions[uniqueCustomId]!.add(
DeviceFunctionData(
entityId: action.entityId,
functionCode: 'automation',
value: action.actionExecutor,
operationName: action.name ?? 'Automation',
),
);
}
}
final ifItems = deviceCards.values
.where((card) => card['type'] == 'condition')
.toList();
final thenItems = deviceCards.values
.where((card) =>
card['type'] == 'action' ||
card['type'] == 'automation' ||
card['type'] == 'scene')
.toList();
emit(state.copyWith(
isLoading: false,
routineName: automationDetails.name,
selectedAutomationOperator: automationDetails.decisionExpr,
effectiveTime: automationDetails.effectiveTime,
isAutomation: true,
thenItems: thenItems,
ifItems: ifItems,
selectedFunctions: updatedFunctions,
automationId: automationDetails.automationId,
));
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'Failed to load automation details: $e',
));
}
} }
} }
@@ -187,8 +187,24 @@ class SetAutomationActionExecutor extends RoutineEvent {
List<Object> get props => [automationActionExecutor]; List<Object> get props => [automationActionExecutor];
} }
class TriggerSwitchTabsEvent extends RoutineEvent {
final bool isRoutineTab;
const TriggerSwitchTabsEvent({required this.isRoutineTab});
@override
List<Object> get props => [isRoutineTab];
}
class CreateNewRoutineViewEvent extends RoutineEvent {
final bool createRoutineView;
const CreateNewRoutineViewEvent({required this.createRoutineView});
@override
List<Object> get props => [createRoutineView];
}
class FetchDevicesInRoutine extends RoutineEvent {} class FetchDevicesInRoutine extends RoutineEvent {}
class ResetRoutineState extends RoutineEvent {} class ResetRoutineState extends RoutineEvent {}
class ClearFunctions extends RoutineEvent {} class ClearFunctions extends RoutineEvent {}
class ResetErrorMessage extends RoutineEvent {}
@@ -22,32 +22,35 @@ class RoutineState extends Equatable {
final String? automationId; final String? automationId;
final bool? isUpdate; final bool? isUpdate;
final List<AllDevicesModel> devices; final List<AllDevicesModel> devices;
final String? automationActionExecutor; // final String? automationActionExecutor;
final bool routineTab;
final bool createRoutineView;
const RoutineState({ const RoutineState(
this.ifItems = const [], {this.ifItems = const [],
this.thenItems = const [], this.thenItems = const [],
this.availableCards = const [], this.availableCards = const [],
this.scenes = const [], this.scenes = const [],
this.automations = const [], this.automations = const [],
this.selectedFunctions = const {}, this.selectedFunctions = const {},
this.isLoading = false, this.isLoading = false,
this.errorMessage, this.errorMessage,
this.routineName, this.routineName,
this.selectedIcon, this.selectedIcon,
this.loadScenesErrorMessage, this.loadScenesErrorMessage,
this.loadAutomationErrorMessage, this.loadAutomationErrorMessage,
this.searchText, this.searchText,
this.isTabToRun = false, this.isTabToRun = false,
this.isAutomation = false, this.isAutomation = false,
this.selectedAutomationOperator = 'or', this.selectedAutomationOperator = 'or',
this.effectiveTime, this.effectiveTime,
this.sceneId, this.sceneId,
this.automationId, this.automationId,
this.isUpdate, this.isUpdate,
this.devices = const [], this.devices = const [],
this.automationActionExecutor, // this.automationActionExecutor,
}); this.routineTab = false,
this.createRoutineView = false});
RoutineState copyWith({ RoutineState copyWith({
List<Map<String, dynamic>>? ifItems, List<Map<String, dynamic>>? ifItems,
@@ -70,35 +73,38 @@ class RoutineState extends Equatable {
String? automationId, String? automationId,
bool? isUpdate, bool? isUpdate,
List<AllDevicesModel>? devices, List<AllDevicesModel>? devices,
String? automationActionExecutor, // String? automationActionExecutor,
TextEditingController? nameController,
bool? routineTab,
bool? createRoutineView,
}) { }) {
return RoutineState( return RoutineState(
ifItems: ifItems ?? this.ifItems, ifItems: ifItems ?? this.ifItems,
thenItems: thenItems ?? this.thenItems, thenItems: thenItems ?? this.thenItems,
scenes: scenes ?? this.scenes, scenes: scenes ?? this.scenes,
automations: automations ?? this.automations, automations: automations ?? this.automations,
selectedFunctions: selectedFunctions ?? this.selectedFunctions, selectedFunctions: selectedFunctions ?? this.selectedFunctions,
isLoading: isLoading ?? this.isLoading, isLoading: isLoading ?? this.isLoading,
errorMessage: errorMessage ?? this.errorMessage, errorMessage: errorMessage ?? this.errorMessage,
routineName: routineName ?? this.routineName, routineName: routineName ?? this.routineName,
selectedIcon: selectedIcon ?? this.selectedIcon, selectedIcon: selectedIcon ?? this.selectedIcon,
loadScenesErrorMessage: loadScenesErrorMessage:
loadScenesErrorMessage ?? this.loadScenesErrorMessage, loadScenesErrorMessage ?? this.loadScenesErrorMessage,
loadAutomationErrorMessage: loadAutomationErrorMessage:
loadAutomationErrorMessage ?? this.loadAutomationErrorMessage, loadAutomationErrorMessage ?? this.loadAutomationErrorMessage,
searchText: searchText ?? this.searchText, searchText: searchText ?? this.searchText,
isTabToRun: isTabToRun ?? this.isTabToRun, isTabToRun: isTabToRun ?? this.isTabToRun,
isAutomation: isAutomation ?? this.isAutomation, isAutomation: isAutomation ?? this.isAutomation,
selectedAutomationOperator: selectedAutomationOperator:
selectedAutomationOperator ?? this.selectedAutomationOperator, selectedAutomationOperator ?? this.selectedAutomationOperator,
effectiveTime: effectiveTime ?? this.effectiveTime, effectiveTime: effectiveTime ?? this.effectiveTime,
sceneId: sceneId ?? this.sceneId, sceneId: sceneId ?? this.sceneId,
automationId: automationId ?? this.automationId, automationId: automationId ?? this.automationId,
isUpdate: isUpdate ?? this.isUpdate, isUpdate: isUpdate ?? this.isUpdate,
devices: devices ?? this.devices, devices: devices ?? this.devices,
automationActionExecutor: // automationActionExecutor: automationActionExecutor ?? this.automationActionExecutor,
automationActionExecutor ?? this.automationActionExecutor, routineTab: routineTab ?? this.routineTab,
); createRoutineView: createRoutineView ?? this.createRoutineView);
} }
@override @override
@@ -123,6 +129,8 @@ class RoutineState extends Equatable {
automationId, automationId,
isUpdate, isUpdate,
devices, devices,
automationActionExecutor, // automationActionExecutor,
routineTab,
createRoutineView
]; ];
} }
@@ -1,3 +1,5 @@
import 'dart:convert';
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:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
@@ -6,10 +8,11 @@ import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SaveRoutineHelper { class SaveRoutineHelper {
static Future<bool?> showSaveRoutineDialog(BuildContext context) async { static Future<void> showSaveRoutineDialog(BuildContext context) async {
return showDialog<bool?>( return showDialog<void>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>( return BlocBuilder<RoutineBloc, RoutineState>(
@@ -54,27 +57,23 @@ class SaveRoutineHelper {
), ),
if (state.isAutomation) if (state.isAutomation)
...state.ifItems.map((item) { ...state.ifItems.map((item) {
final functions = state.selectedFunctions[ final functions =
item['uniqueCustomId']] ?? state.selectedFunctions[item['uniqueCustomId']] ?? [];
[];
return ListTile( return ListTile(
leading: SvgPicture.asset( leading: SvgPicture.asset(
item['imagePath'], item['imagePath'],
width: 22, width: 22,
height: 22, height: 22,
), ),
title: Text(item['title'], title:
style: const TextStyle(fontSize: 14)), Text(item['title'], style: const TextStyle(fontSize: 14)),
subtitle: Wrap( subtitle: Wrap(
children: functions children: functions
.map((f) => Text( .map((f) => Text(
'${f.operationName}: ${f.value}, ', '${f.operationName}: ${f.value}, ',
style: const TextStyle( style: const TextStyle(
color: ColorsManager color: ColorsManager.grayColor, fontSize: 8),
.grayColor, overflow: TextOverflow.ellipsis,
fontSize: 8),
overflow:
TextOverflow.ellipsis,
maxLines: 3, maxLines: 3,
)) ))
.toList(), .toList(),
@@ -99,25 +98,33 @@ class SaveRoutineHelper {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
...state.thenItems.map((item) { ...state.thenItems.map((item) {
final functions = state.selectedFunctions[ final functions =
item['uniqueCustomId']] ?? state.selectedFunctions[item['uniqueCustomId']] ?? [];
[];
return ListTile( return ListTile(
leading: SvgPicture.asset( leading: item['type'] == 'tap_to_run'
item['imagePath'], ? Image.memory(
width: 22, base64Decode(item['icon']),
height: 22, 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( subtitle: Wrap(
children: functions children: functions
.map((f) => Text( .map((f) => Text(
'${f.operationName}: ${f.value}, ', '${f.operationName}: ${f.value}, ',
style: const TextStyle( style: context.textTheme.bodySmall?.copyWith(
color: color: ColorsManager.grayColor, fontSize: 8),
ColorsManager.grayColor,
fontSize: 8),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 3, maxLines: 3,
)) ))
@@ -131,42 +138,33 @@ class SaveRoutineHelper {
], ],
), ),
), ),
if (state.errorMessage != null) // if (state.errorMessage != null || state.errorMessage!.isNotEmpty)
Padding( // Padding(
padding: const EdgeInsets.all(8.0), // padding: const EdgeInsets.all(8.0),
child: Text( // child: Text(
state.errorMessage!, // state.errorMessage!,
style: const TextStyle(color: Colors.red), // style: const TextStyle(color: Colors.red),
), // ),
), // ),
DialogFooter( DialogFooter(
onCancel: () => Navigator.pop(context, false), onCancel: () => Navigator.pop(context),
onConfirm: () { onConfirm: () async {
if (state.isAutomation) { if (state.isAutomation) {
if (state.automationId != null) { if (state.automationId != null) {
context context.read<RoutineBloc>().add(const UpdateAutomation());
.read<RoutineBloc>()
.add(const UpdateAutomation());
} else { } else {
context context.read<RoutineBloc>().add(const CreateAutomationEvent());
.read<RoutineBloc>()
.add(const CreateAutomationEvent());
} }
} else { } else {
if (state.sceneId != null) { if (state.sceneId != null) {
context context.read<RoutineBloc>().add(const UpdateScene());
.read<RoutineBloc>()
.add(const UpdateScene());
} else { } else {
context context.read<RoutineBloc>().add(const CreateSceneEvent());
.read<RoutineBloc>()
.add(const CreateSceneEvent());
}
if (context.read<RoutineBloc>().state.errorMessage ==
null) {
Navigator.pop(context, true);
} }
} }
// if (state.errorMessage == null || state.errorMessage!.isEmpty) {
Navigator.pop(context);
// }
}, },
isConfirmEnabled: true, isConfirmEnabled: true,
), ),
@@ -92,7 +92,7 @@ class Condition {
return { return {
'code': code, 'code': code,
'entityId': entityId, 'entityId': entityId,
'entityType': entityType, 'entityType': 'device_report',
'expr': expr.toMap(), 'expr': expr.toMap(),
}; };
} }
@@ -137,11 +137,13 @@ class ConditionExpr {
class AutomationAction { class AutomationAction {
String entityId; String entityId;
String? actionType;
String actionExecutor; String actionExecutor;
ExecutorProperty? executorProperty; ExecutorProperty? executorProperty;
AutomationAction({ AutomationAction({
required this.entityId, required this.entityId,
this.actionType,
required this.actionExecutor, required this.actionExecutor,
this.executorProperty, this.executorProperty,
}); });
@@ -150,12 +152,15 @@ class AutomationAction {
return { return {
'entityId': entityId, 'entityId': entityId,
'actionExecutor': actionExecutor, 'actionExecutor': actionExecutor,
'executorProperty': executorProperty?.toMap(), if (executorProperty != null)
'executorProperty': executorProperty?.toMap(),
'actionType': actionType
}; };
} }
factory AutomationAction.fromMap(Map<String, dynamic> map) { factory AutomationAction.fromMap(Map<String, dynamic> map) {
return AutomationAction( return AutomationAction(
actionType: map['actionType'],
entityId: map['entityId'] ?? '', entityId: map['entityId'] ?? '',
actionExecutor: map['actionExecutor'] ?? '', actionExecutor: map['actionExecutor'] ?? '',
executorProperty: map['executorProperty'] != null executorProperty: map['executorProperty'] != null
@@ -95,10 +95,12 @@ class CreateSceneModel {
class CreateSceneAction { class CreateSceneAction {
String entityId; String entityId;
String? actionType;
String actionExecutor; String actionExecutor;
CreateSceneExecutorProperty? executorProperty; CreateSceneExecutorProperty? executorProperty;
CreateSceneAction({ CreateSceneAction({
this.actionType,
required this.entityId, required this.entityId,
required this.actionExecutor, required this.actionExecutor,
required this.executorProperty, required this.executorProperty,
@@ -110,6 +112,7 @@ class CreateSceneAction {
CreateSceneExecutorProperty? executorProperty, CreateSceneExecutorProperty? executorProperty,
}) { }) {
return CreateSceneAction( return CreateSceneAction(
actionType: actionType ?? this.actionType,
entityId: entityId ?? this.entityId, entityId: entityId ?? this.entityId,
actionExecutor: actionExecutor ?? this.actionExecutor, actionExecutor: actionExecutor ?? this.actionExecutor,
executorProperty: executorProperty ?? this.executorProperty, executorProperty: executorProperty ?? this.executorProperty,
@@ -125,6 +128,7 @@ class CreateSceneAction {
}; };
} else { } else {
return { return {
"actionType": actionType,
'entityId': entityId, 'entityId': entityId,
'actionExecutor': actionExecutor, 'actionExecutor': actionExecutor,
}; };
@@ -133,6 +137,7 @@ class CreateSceneAction {
factory CreateSceneAction.fromMap(Map<String, dynamic> map) { factory CreateSceneAction.fromMap(Map<String, dynamic> map) {
return CreateSceneAction( return CreateSceneAction(
actionType: map['actionType'],
entityId: map['entityId'] ?? '', entityId: map['entityId'] ?? '',
actionExecutor: map['actionExecutor'] ?? '', actionExecutor: map['actionExecutor'] ?? '',
executorProperty: executorProperty:
+6 -9
View File
@@ -1,6 +1,5 @@
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/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart'; import 'package:syncrow_web/pages/routiens/view/create_new_routine_view.dart';
import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart'; import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/fetch_routine_scenes_automation.dart';
@@ -23,12 +22,10 @@ class _RoutinesViewState extends State<RoutinesView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<SwitchTabsBloc, SwitchTabsState>( return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) { builder: (context, state) {
if (state is ShowCreateRoutineState && state.showCreateRoutine) { if (state.createRoutineView) {
return const CreateNewRoutineView( return const CreateNewRoutineView();
);
} }
return Padding( return Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
@@ -49,12 +46,12 @@ class _RoutinesViewState extends State<RoutinesView> {
), ),
RoutineViewCard( RoutineViewCard(
onTap: () { onTap: () {
BlocProvider.of<SwitchTabsBloc>(context).add(
const CreateNewRoutineViewEvent(true),
);
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
(ResetRoutineState()), (ResetRoutineState()),
); );
BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(createRoutineView: true),
);
}, },
icon: Icons.add, icon: Icons.add,
textString: '', textString: '',
+4 -11
View File
@@ -33,31 +33,24 @@ class DeleteSceneWidget extends StatelessWidget {
alignment: AlignmentDirectional.center, alignment: AlignmentDirectional.center,
child: Text( child: Text(
'Cancel', 'Cancel',
style: Theme.of(context) style: Theme.of(context).textTheme.bodyMedium!.copyWith(
.textTheme
.bodyMedium!
.copyWith(
color: ColorsManager.textGray, color: ColorsManager.textGray,
), ),
), ),
), ),
), ),
Container( Container(width: 1, height: 50, color: ColorsManager.greyColor),
width: 1, height: 50, color: ColorsManager.greyColor),
InkWell( InkWell(
onTap: () { onTap: () {
context.read<RoutineBloc>().add(const DeleteScene()); context.read<RoutineBloc>().add(const DeleteScene());
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.of(context).pop(true); Navigator.of(context).pop();
}, },
child: Container( child: Container(
alignment: AlignmentDirectional.center, alignment: AlignmentDirectional.center,
child: Text( child: Text(
'Confirm', 'Confirm',
style: Theme.of(context) style: Theme.of(context).textTheme.bodyMedium!.copyWith(
.textTheme
.bodyMedium!
.copyWith(
color: ColorsManager.primaryColorWithOpacity, color: ColorsManager.primaryColorWithOpacity,
), ),
), ),
+20 -10
View File
@@ -35,6 +35,20 @@ class DraggableCard extends StatelessWidget {
final deviceFunctions = final deviceFunctions =
state.selectedFunctions[deviceData['uniqueCustomId']] ?? []; 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>>( return Draggable<Map<String, dynamic>>(
data: deviceData, data: deviceData,
feedback: Transform.rotate( feedback: Transform.rotate(
@@ -79,17 +93,13 @@ class DraggableCard extends StatelessWidget {
), ),
), ),
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: imagePath.contains('.svg') child: deviceData['type'] == 'tap_to_run'
? SvgPicture.asset( ? Image.memory(
imagePath, base64Decode(deviceData['icon']),
) )
: imagePath.contains('.png') : SvgPicture.asset(
? Image.asset( imagePath,
imagePath, ),
)
: Image.memory(
base64Decode(imagePath),
),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Padding( Padding(
@@ -1,6 +1,5 @@
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/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart'; import 'package:syncrow_web/pages/routiens/widgets/main_routine_view/routine_view_card.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
@@ -69,8 +68,8 @@ class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
), ),
child: RoutineViewCard( child: RoutineViewCard(
onTap: () { onTap: () {
BlocProvider.of<SwitchTabsBloc>(context).add( BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(true), const CreateNewRoutineViewEvent(createRoutineView: true),
); );
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
GetSceneDetails( GetSceneDetails(
@@ -118,8 +117,8 @@ class _FetchRoutineScenesState extends State<FetchRoutineScenesAutomation>
), ),
child: RoutineViewCard( child: RoutineViewCard(
onTap: () { onTap: () {
BlocProvider.of<SwitchTabsBloc>(context).add( BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(true), const CreateNewRoutineViewEvent(createRoutineView: true),
); );
context.read<RoutineBloc>().add( context.read<RoutineBloc>().add(
GetAutomationDetails( GetAutomationDetails(
@@ -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/pages/routiens/widgets/dialog_footer.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
class AutomationDialog extends StatelessWidget { class AutomationDialog extends StatefulWidget {
final String automationName; final String automationName;
final String automationId; final String automationId;
final String uniqueCustomId; final String uniqueCustomId;
final String? passedAutomationActionExecutor;
const AutomationDialog({ const AutomationDialog({
super.key, super.key,
required this.automationName, required this.automationName,
required this.automationId, required this.automationId,
required this.uniqueCustomId, required this.uniqueCustomId,
this.passedAutomationActionExecutor,
}); });
@override @override
Widget build(BuildContext context) { State<AutomationDialog> createState() => _AutomationDialogState();
return BlocBuilder<RoutineBloc, RoutineState>( }
builder: (context, state) {
final isEnabled = state.automationActionExecutor == 'rule_enable';
return Dialog( class _AutomationDialogState extends State<AutomationDialog> {
shape: String? selectedAutomationActionExecutor;
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Container( @override
width: 400, void initState() {
padding: const EdgeInsets.all(16), super.initState();
child: Column( List<DeviceFunctionData>? functions = context
mainAxisSize: MainAxisSize.min, .read<RoutineBloc>()
children: [ .state
DialogHeader(automationName), .selectedFunctions[widget.uniqueCustomId];
const SizedBox(height: 16), for (DeviceFunctionData data in functions ?? []) {
ListTile( if (data.entityId == widget.automationId) {
leading: selectedAutomationActionExecutor = data.value;
SvgPicture.asset(Assets.acPower, width: 24, height: 24), }
title: const Text('Enable'), }
trailing: Radio<bool>( }
value: true,
groupValue: isEnabled, @override
onChanged: (bool? value) { Widget build(BuildContext context) {
if (value == true) { return Dialog(
context.read<RoutineBloc>().add( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
const SetAutomationActionExecutor( child: Container(
automationActionExecutor: 'rule_enable', width: 400,
), padding: const EdgeInsets.all(16),
); child: Column(
} mainAxisSize: MainAxisSize.min,
}, children: [
), DialogHeader(widget.automationName),
), const SizedBox(height: 16),
ListTile( ListTile(
leading: SvgPicture.asset(Assets.acPowerOff, leading: SvgPicture.asset(Assets.acPower, width: 24, height: 24),
width: 24, height: 24), title: const Text('Enable'),
title: const Text('Disable'), trailing: Radio<String?>(
trailing: Radio<bool>( value: 'rule_enable',
value: false, groupValue: selectedAutomationActionExecutor,
groupValue: isEnabled, onChanged: (String? value) {
onChanged: (bool? value) { setState(() {
if (value == false) { selectedAutomationActionExecutor = 'rule_enable';
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,
),
],
), ),
), 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,
),
],
),
),
); );
} }
} }
@@ -1,6 +1,5 @@
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/routiens/bloc/routine_bloc/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.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';
@@ -49,11 +48,11 @@ class DiscardDialog {
onConfirm: () { onConfirm: () {
context.read<RoutineBloc>().add(ResetRoutineState()); context.read<RoutineBloc>().add(ResetRoutineState());
Navigator.pop(context); Navigator.pop(context);
BlocProvider.of<SwitchTabsBloc>(context).add( BlocProvider.of<RoutineBloc>(context).add(
const CreateNewRoutineViewEvent(false), const CreateNewRoutineViewEvent(createRoutineView: false),
); );
BlocProvider.of<SwitchTabsBloc>(context).add( BlocProvider.of<RoutineBloc>(context).add(
const TriggerSwitchTabsEvent(true), const TriggerSwitchTabsEvent(isRoutineTab: true),
); );
}); });
} }
@@ -1,7 +1,6 @@
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/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.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/routine_bloc.dart'; import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
import 'package:syncrow_web/pages/routiens/helper/save_routine_helper.dart'; import 'package:syncrow_web/pages/routiens/helper/save_routine_helper.dart';
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/discard_dialog.dart'; import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/discard_dialog.dart';
@@ -16,8 +15,7 @@ class RoutineSearchAndButtons extends StatefulWidget {
}); });
@override @override
State<RoutineSearchAndButtons> createState() => State<RoutineSearchAndButtons> createState() => _RoutineSearchAndButtonsState();
_RoutineSearchAndButtonsState();
} }
class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> { class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
@@ -37,20 +35,21 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocConsumer<RoutineBloc, RoutineState>( return BlocBuilder<RoutineBloc, RoutineState>(
listenWhen: (previous, current) =>
previous.routineName != current.routineName,
listener: (context, state) {
if (state.routineName != _nameController.text) {
_nameController.text = state.routineName ?? '';
}
},
builder: (context, state) { builder: (context, state) {
_nameController.text = state.routineName ?? '';
return LayoutBuilder( return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) { builder: (BuildContext context, BoxConstraints constraints) {
return Wrap( return Wrap(
runSpacing: 16, runSpacing: 16,
children: [ children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
state.errorMessage ?? '',
style: const TextStyle(color: Colors.red),
),
),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
@@ -62,9 +61,8 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: constraints.maxWidth > 700 maxWidth:
? 450 constraints.maxWidth > 700 ? 450 : constraints.maxWidth - 32),
: constraints.maxWidth - 32),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -73,13 +71,10 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
children: [ children: [
Text('* ', Text('* ',
style: context.textTheme.bodyMedium! style: context.textTheme.bodyMedium!
.copyWith( .copyWith(color: ColorsManager.red, fontSize: 13)),
color: ColorsManager.red,
fontSize: 13)),
Text( Text(
'Routine Name', 'Routine Name',
style: context.textTheme.bodyMedium! style: context.textTheme.bodyMedium!.copyWith(
.copyWith(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
@@ -93,24 +88,20 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
decoration: containerWhiteDecoration, decoration: containerWhiteDecoration,
child: TextFormField( child: TextFormField(
style: context.textTheme.bodyMedium! style: context.textTheme.bodyMedium!
.copyWith( .copyWith(color: ColorsManager.blackColor),
color: ColorsManager.blackColor),
controller: _nameController, controller: _nameController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Please enter the name', hintText: 'Please enter the name',
hintStyle: context.textTheme.bodyMedium! hintStyle: context.textTheme.bodyMedium!
.copyWith( .copyWith(fontSize: 12, color: ColorsManager.grayColor),
fontSize: 12,
color: ColorsManager.grayColor),
contentPadding: contentPadding:
const EdgeInsets.symmetric( const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
horizontal: 12, vertical: 10),
border: InputBorder.none, border: InputBorder.none,
), ),
onChanged: (value) { onTapOutside: (_) {
context context
.read<RoutineBloc>() .read<RoutineBloc>()
.add(SetRoutineName(value)); .add(SetRoutineName(_nameController.text));
}, },
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
@@ -130,18 +121,16 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
width: 200, width: 200,
child: Center( child: Center(
child: DefaultButton( child: DefaultButton(
onPressed: state.isAutomation || onPressed: state.isAutomation || state.isTabToRun
state.isTabToRun
? () async { ? () async {
final result = await SettingHelper final result = await SettingHelper.showSettingDialog(
.showSettingDialog(
context: context, context: context,
iconId: iconId: state.selectedIcon ?? '',
state.selectedIcon ?? '',
); );
if (result != null) { if (result != null) {
context.read<RoutineBloc>().add( context
AddSelectedIcon(result)); .read<RoutineBloc>()
.add(AddSelectedIcon(result));
} }
} }
: null, : null,
@@ -197,12 +186,10 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
child: Center( child: Center(
child: DefaultButton( child: DefaultButton(
onPressed: () async { onPressed: () async {
if (state.routineName == null || if (state.routineName == null || state.routineName!.isEmpty) {
state.routineName!.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: const Text( content: const Text('Please enter the routine name'),
'Please enter the routine name'),
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
backgroundColor: ColorsManager.red, backgroundColor: ColorsManager.red,
action: SnackBarAction( action: SnackBarAction(
@@ -216,12 +203,10 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
return; return;
} }
if (state.ifItems.isEmpty || if (state.ifItems.isEmpty || state.thenItems.isEmpty) {
state.thenItems.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: const Text( content: const Text('Please add if and then condition'),
'Please add if and then condition'),
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
backgroundColor: ColorsManager.red, backgroundColor: ColorsManager.red,
action: SnackBarAction( action: SnackBarAction(
@@ -234,18 +219,18 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
); );
return; return;
} }
final result = await SaveRoutineHelper // final result =
.showSaveRoutineDialog(context); // await
if (result != null && result) { BlocProvider.of<RoutineBloc>(context).add(ResetErrorMessage());
BlocProvider.of<SwitchTabsBloc>(context) SaveRoutineHelper.showSaveRoutineDialog(context);
.add( // if (result != null && result) {
const CreateNewRoutineViewEvent(false), // BlocProvider.of<RoutineBloc>(context).add(
); // const CreateNewRoutineViewEvent(createRoutineView: false),
BlocProvider.of<SwitchTabsBloc>(context) // );
.add( // BlocProvider.of<RoutineBloc>(context).add(
const TriggerSwitchTabsEvent(true), // const TriggerSwitchTabsEvent(isRoutineTab: true),
); // );
} // }
}, },
borderRadius: 15, borderRadius: 15,
elevation: 0, elevation: 0,
@@ -276,14 +261,10 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
child: DefaultButton( child: DefaultButton(
onPressed: state.isAutomation || state.isTabToRun onPressed: state.isAutomation || state.isTabToRun
? () async { ? () async {
final result = final result = await SettingHelper.showSettingDialog(
await SettingHelper.showSettingDialog( context: context, iconId: state.selectedIcon ?? '');
context: context,
iconId: state.selectedIcon ?? '');
if (result != null) { if (result != null) {
context context.read<RoutineBloc>().add(AddSelectedIcon(result));
.read<RoutineBloc>()
.add(AddSelectedIcon(result));
} }
} }
: null, : null,
@@ -333,12 +314,10 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
child: Center( child: Center(
child: DefaultButton( child: DefaultButton(
onPressed: () async { onPressed: () async {
if (state.routineName == null || if (state.routineName == null || state.routineName!.isEmpty) {
state.routineName!.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: const Text( content: const Text('Please enter the routine name'),
'Please enter the routine name'),
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
backgroundColor: ColorsManager.red, backgroundColor: ColorsManager.red,
action: SnackBarAction( action: SnackBarAction(
@@ -352,12 +331,10 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
return; return;
} }
if (state.ifItems.isEmpty || if (state.ifItems.isEmpty || state.thenItems.isEmpty) {
state.thenItems.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: const Text( content: const Text('Please add if and then condition'),
'Please add if and then condition'),
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
backgroundColor: ColorsManager.red, backgroundColor: ColorsManager.red,
action: SnackBarAction( action: SnackBarAction(
@@ -370,17 +347,18 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
); );
return; return;
} }
final result = // final result =
await SaveRoutineHelper.showSaveRoutineDialog( // await
context); BlocProvider.of<RoutineBloc>(context).add(ResetErrorMessage());
if (result != null && result) { SaveRoutineHelper.showSaveRoutineDialog(context);
BlocProvider.of<SwitchTabsBloc>(context).add( // if (result != null && result) {
const CreateNewRoutineViewEvent(false), // BlocProvider.of<RoutineBloc>(context).add(
); // const CreateNewRoutineViewEvent(createRoutineView: false),
BlocProvider.of<SwitchTabsBloc>(context).add( // );
const TriggerSwitchTabsEvent(true), // BlocProvider.of<RoutineBloc>(context).add(
); // const TriggerSwitchTabsEvent(isRoutineTab: true),
} // );
// }
}, },
borderRadius: 15, borderRadius: 15,
elevation: 0, elevation: 0,
+17 -3
View File
@@ -87,8 +87,11 @@ class ThenContainer extends StatelessWidget {
...state.thenItems[index], ...state.thenItems[index],
'imagePath': 'imagePath':
Assets.automation, Assets.automation,
'title': state 'title':
.thenItems[index]['name'], state.thenItems[index]
['name'] ??
state.thenItems[index]
['title'],
})); }));
} }
return; return;
@@ -149,6 +152,12 @@ class ThenContainer extends StatelessWidget {
} }
if (mutableData['type'] == 'automation') { if (mutableData['type'] == 'automation') {
int index = state.thenItems.indexWhere(
(item) => item['deviceId'] == mutableData['deviceId']);
if (index != -1) {
return;
}
final result = await showDialog<bool>( final result = await showDialog<bool>(
context: context, context: context,
builder: (BuildContext context) => AutomationDialog( builder: (BuildContext context) => AutomationDialog(
@@ -169,9 +178,14 @@ class ThenContainer extends StatelessWidget {
} }
if (mutableData['type'] == 'tap_to_run' && state.isAutomation) { 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({ context.read<RoutineBloc>().add(AddToThenContainer({
...mutableData, ...mutableData,
'imagePath': Assets.logo, 'imagePath': mutableData['imagePath'] ?? Assets.logo,
'title': mutableData['name'], 'title': mutableData['name'],
})); }));
+34 -2
View File
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/navigation_service.dart'; import 'package:syncrow_web/utils/navigation_service.dart';
class CustomSnackBar { 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) { static greenSnackBar(String message) {
final key = NavigationService.snackbarKey; final key = NavigationService.snackbarKey;
BuildContext? currentContext = key?.currentContext; BuildContext? currentContext = key?.currentContext;
@@ -29,8 +59,10 @@ class CustomSnackBar {
), ),
Text( Text(
message, message,
style: Theme.of(currentContext).textTheme.bodySmall!.copyWith( style: Theme.of(currentContext)
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green), .textTheme
.bodySmall!
.copyWith(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green),
) )
]), ]),
); );