push fixes and merge

This commit is contained in:
ashraf_personal
2024-12-03 01:02:13 +03:00
7 changed files with 162 additions and 67 deletions

View File

@ -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/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/snack_bar.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
part 'routine_event.dart'; part 'routine_event.dart';
@ -46,6 +47,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
on<UpdateAutomation>(_onUpdateAutomation); on<UpdateAutomation>(_onUpdateAutomation);
on<TriggerSwitchTabsEvent>(_triggerSwitchTabsEvent); on<TriggerSwitchTabsEvent>(_triggerSwitchTabsEvent);
on<CreateNewRoutineViewEvent>(_createNewRoutineViewEvent); on<CreateNewRoutineViewEvent>(_createNewRoutineViewEvent);
on<ResetErrorMessage>(_resetErrorMessage);
} }
FutureOr<void> _triggerSwitchTabsEvent( FutureOr<void> _triggerSwitchTabsEvent(
@ -61,6 +63,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
_resetErrorMessage(
ResetErrorMessage event,
Emitter<RoutineState> emit,
) {
emit(state.copyWith(errorMessage: ''));
}
FutureOr<void> _createNewRoutineViewEvent( FutureOr<void> _createNewRoutineViewEvent(
CreateNewRoutineViewEvent event, CreateNewRoutineViewEvent event,
Emitter<RoutineState> emit, Emitter<RoutineState> emit,
@ -216,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;
@ -230,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;
@ -320,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));
@ -433,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');
} }
} }
@ -850,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;
@ -943,13 +961,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
)); ));
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; // return;
} // }
if (_isLastActionDelay(state.thenItems)) { if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith( emit(state.copyWith(

View File

@ -206,3 +206,5 @@ class FetchDevicesInRoutine extends RoutineEvent {}
class ResetRoutineState extends RoutineEvent {} class ResetRoutineState extends RoutineEvent {}
class ClearFunctions extends RoutineEvent {} class ClearFunctions extends RoutineEvent {}
class ResetErrorMessage extends RoutineEvent {}

View File

@ -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,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/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<void> showSaveRoutineDialog(BuildContext context) async { static Future<void> showSaveRoutineDialog(BuildContext context) async {
@ -98,18 +101,29 @@ class SaveRoutineHelper {
final functions = final functions =
state.selectedFunctions[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: ColorsManager.grayColor, fontSize: 8), color: ColorsManager.grayColor, fontSize: 8),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 3, maxLines: 3,
@ -124,17 +138,17 @@ class SaveRoutineHelper {
], ],
), ),
), ),
if (state.errorMessage != null || state.errorMessage!.isNotEmpty) // 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), onCancel: () => Navigator.pop(context),
onConfirm: () { onConfirm: () async {
if (state.isAutomation) { if (state.isAutomation) {
if (state.automationId != null) { if (state.automationId != null) {
context.read<RoutineBloc>().add(const UpdateAutomation()); context.read<RoutineBloc>().add(const UpdateAutomation());
@ -148,10 +162,9 @@ class SaveRoutineHelper {
context.read<RoutineBloc>().add(const CreateSceneEvent()); context.read<RoutineBloc>().add(const CreateSceneEvent());
} }
} }
if (context.read<RoutineBloc>().state.errorMessage == null || // if (state.errorMessage == null || state.errorMessage!.isEmpty) {
context.read<RoutineBloc>().state.errorMessage!.isEmpty) { Navigator.pop(context);
Navigator.pop(context); // }
}
}, },
isConfirmEnabled: true, isConfirmEnabled: true,
), ),

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(

View File

@ -43,6 +43,13 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
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: [
@ -214,6 +221,7 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
} }
// final result = // final result =
// await // await
BlocProvider.of<RoutineBloc>(context).add(ResetErrorMessage());
SaveRoutineHelper.showSaveRoutineDialog(context); SaveRoutineHelper.showSaveRoutineDialog(context);
// if (result != null && result) { // if (result != null && result) {
// BlocProvider.of<RoutineBloc>(context).add( // BlocProvider.of<RoutineBloc>(context).add(
@ -341,6 +349,7 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
} }
// final result = // final result =
// await // await
BlocProvider.of<RoutineBloc>(context).add(ResetErrorMessage());
SaveRoutineHelper.showSaveRoutineDialog(context); SaveRoutineHelper.showSaveRoutineDialog(context);
// if (result != null && result) { // if (result != null && result) {
// BlocProvider.of<RoutineBloc>(context).add( // BlocProvider.of<RoutineBloc>(context).add(

View File

@ -152,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(
@ -172,6 +178,11 @@ 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': mutableData['imagePath'] ?? Assets.logo, 'imagePath': mutableData['imagePath'] ?? Assets.logo,

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),
) )
]), ]),
); );