diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 673b439c..a86b49c5 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -13,6 +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/routines_api.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/snack_bar.dart'; import 'package:uuid/uuid.dart'; part 'routine_event.dart'; @@ -46,6 +47,7 @@ class RoutineBloc extends Bloc { on(_onUpdateAutomation); on(_triggerSwitchTabsEvent); on(_createNewRoutineViewEvent); + on(_resetErrorMessage); } FutureOr _triggerSwitchTabsEvent( @@ -61,6 +63,13 @@ class RoutineBloc extends Bloc { } } + _resetErrorMessage( + ResetErrorMessage event, + Emitter emit, + ) { + emit(state.copyWith(errorMessage: '')); + } + FutureOr _createNewRoutineViewEvent( CreateNewRoutineViewEvent event, Emitter emit, @@ -216,10 +225,10 @@ class RoutineBloc extends Bloc { emit(state.copyWith(selectedIcon: event.icon)); } - bool _isFirstActionDelay(List> actions) { - if (actions.isEmpty) return false; - return actions.first['deviceId'] == 'delay'; - } + // bool _isFirstActionDelay(List> actions) { + // if (actions.isEmpty) return false; + // return actions.first['deviceId'] == 'delay'; + // } bool _isLastActionDelay(List> actions) { if (actions.isEmpty) return false; @@ -230,17 +239,18 @@ class RoutineBloc extends Bloc { CreateSceneEvent event, Emitter emit) async { try { // Check if first action is delay - if (_isFirstActionDelay(state.thenItems)) { - emit(state.copyWith( - errorMessage: 'Cannot have delay as the first action', - isLoading: false, - )); - return; - } + // if (_isFirstActionDelay(state.thenItems)) { + // emit(state.copyWith( + // errorMessage: 'Cannot have delay as the first action', + // isLoading: false, + // )); + // return; + // } if (_isLastActionDelay(state.thenItems)) { 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, )); return; @@ -320,21 +330,26 @@ class RoutineBloc extends Bloc { emit(state.copyWith( errorMessage: 'Automation name is required', )); + CustomSnackBar.redSnackBar('Automation name is required'); return; } - if (_isFirstActionDelay(state.thenItems)) { - emit(state.copyWith( - errorMessage: 'Cannot have delay as the first action', - isLoading: false, - )); - return; - } + // if (_isFirstActionDelay(state.thenItems)) { + // emit(state.copyWith( + // errorMessage: 'Cannot have delay as the first action', + // isLoading: false, + // )); + // CustomSnackBar.redSnackBar('Cannot have delay as the first action'); + + // return; + // } if (_isLastActionDelay(state.thenItems)) { 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, )); + CustomSnackBar.redSnackBar('Cannot have delay as the last action'); return; } emit(state.copyWith(isLoading: true, errorMessage: null)); @@ -433,12 +448,14 @@ class RoutineBloc extends Bloc { isLoading: false, errorMessage: result['message'], )); + CustomSnackBar.redSnackBar('Something went wrong'); } } catch (e) { emit(state.copyWith( isLoading: false, errorMessage: 'Something went wrong', )); + CustomSnackBar.redSnackBar('Something went wrong'); } } @@ -850,18 +867,19 @@ class RoutineBloc extends Bloc { UpdateScene event, Emitter emit) async { try { // Check if first action is delay - if (_isFirstActionDelay(state.thenItems)) { - emit(state.copyWith( - errorMessage: 'Cannot have delay as the first action', - isLoading: false, - )); + // if (_isFirstActionDelay(state.thenItems)) { + // emit(state.copyWith( + // errorMessage: 'Cannot have delay as the first action', + // isLoading: false, + // )); - return; - } + // return; + // } if (_isLastActionDelay(state.thenItems)) { 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, )); return; @@ -943,13 +961,13 @@ class RoutineBloc extends Bloc { )); return; } - if (_isFirstActionDelay(state.thenItems)) { - emit(state.copyWith( - errorMessage: 'Cannot have delay as the first action', - isLoading: false, - )); - return; - } + // if (_isFirstActionDelay(state.thenItems)) { + // emit(state.copyWith( + // errorMessage: 'Cannot have delay as the first action', + // isLoading: false, + // )); + // return; + // } if (_isLastActionDelay(state.thenItems)) { emit(state.copyWith( diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 1f703c92..f3d35eb8 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -206,3 +206,5 @@ class FetchDevicesInRoutine extends RoutineEvent {} class ResetRoutineState extends RoutineEvent {} class ClearFunctions extends RoutineEvent {} + +class ResetErrorMessage extends RoutineEvent {} diff --git a/lib/pages/routiens/helper/save_routine_helper.dart b/lib/pages/routiens/helper/save_routine_helper.dart index 3bd5a5d2..6a725145 100644 --- a/lib/pages/routiens/helper/save_routine_helper.dart +++ b/lib/pages/routiens/helper/save_routine_helper.dart @@ -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 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().add(const UpdateAutomation()); @@ -148,10 +162,9 @@ class SaveRoutineHelper { context.read().add(const CreateSceneEvent()); } } - if (context.read().state.errorMessage == null || - context.read().state.errorMessage!.isEmpty) { - Navigator.pop(context); - } + // if (state.errorMessage == null || state.errorMessage!.isEmpty) { + Navigator.pop(context); + // } }, isConfirmEnabled: true, ), diff --git a/lib/pages/routiens/widgets/dragable_card.dart b/lib/pages/routiens/widgets/dragable_card.dart index 1eddf841..e26d3d12 100644 --- a/lib/pages/routiens/widgets/dragable_card.dart +++ b/lib/pages/routiens/widgets/dragable_card.dart @@ -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>( 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( diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index 4f4165d5..f10694b6 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -43,6 +43,13 @@ class _RoutineSearchAndButtonsState extends State { 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 { } // final result = // await + BlocProvider.of(context).add(ResetErrorMessage()); SaveRoutineHelper.showSaveRoutineDialog(context); // if (result != null && result) { // BlocProvider.of(context).add( @@ -341,6 +349,7 @@ class _RoutineSearchAndButtonsState extends State { } // final result = // await + BlocProvider.of(context).add(ResetErrorMessage()); SaveRoutineHelper.showSaveRoutineDialog(context); // if (result != null && result) { // BlocProvider.of(context).add( diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index 69a1c278..8a12e443 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/widgets/then_container.dart @@ -152,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( context: context, builder: (BuildContext context) => AutomationDialog( @@ -172,6 +178,11 @@ 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().add(AddToThenContainer({ ...mutableData, 'imagePath': mutableData['imagePath'] ?? Assets.logo, diff --git a/lib/utils/snack_bar.dart b/lib/utils/snack_bar.dart index d50a4250..0a312e5a 100644 --- a/lib/utils/snack_bar.dart +++ b/lib/utils/snack_bar.dart @@ -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), ) ]), );