diff --git a/.env.development b/.env.development index e77609dc..1fd358ec 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ ENV_NAME=development -BASE_URL=https://syncrow-dev.azurewebsites.net \ No newline at end of file +BASE_URL=http://localhost:4001 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 29a3a501..ae866139 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release +lib/utils/constants/temp_const.dart +.env.development +lib/utils/constants/temp_const.dart diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index 2434a32c..0c002e7d 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -329,6 +329,7 @@ class SpaceManagementBloc Emitter emit, ) { final communities = List.from(previousState.communities); + for (var community in communities) { if (community.uuid == communityUuid) { @@ -425,7 +426,7 @@ class SpaceManagementBloc emit(SpaceManagementLoading()); try { List communities = await _api.fetchCommunities(); - + List updatedCommunities = await Future.wait( communities.map((community) async { List spaces = diff --git a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart index 6bfd3ee3..5039340c 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/loaded_space_widget.dart @@ -48,7 +48,8 @@ class _LoadedStateViewState extends State { hasSpaceModels ? Expanded( child: SpaceModelPage( - spaceModels: widget.spaceModels ??[], + spaceModels: widget.spaceModels ?? [], + products: widget.products, )) : CommunityStructureArea( selectedCommunity: widget.selectedCommunity, diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart new file mode 100644 index 00000000..e39d3b3d --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_bloc.dart @@ -0,0 +1,26 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/create_space_model_state.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +class CreateSpaceModelBloc extends Bloc { + SpaceTemplateModel? _space; + + CreateSpaceModelBloc() : super(CreateSpaceModelInitial()) { + on((event, emit) { + emit(CreateSpaceModelLoading()); + Future.delayed(const Duration(seconds: 1), () { + if (_space != null) { + emit(CreateSpaceModelLoaded(_space!)); + } else { + emit(CreateSpaceModelError("No space template found")); + } + }); + }); + + on((event, emit) { + _space = event.spaceTemplate; + emit(CreateSpaceModelLoaded(_space!)); + }); + } +} \ No newline at end of file diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart new file mode 100644 index 00000000..f3cee617 --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_event.dart @@ -0,0 +1,11 @@ +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +abstract class CreateSpaceModelEvent {} + +class LoadSpaceTemplate extends CreateSpaceModelEvent {} + +class UpdateSpaceTemplate extends CreateSpaceModelEvent { + final SpaceTemplateModel spaceTemplate; + + UpdateSpaceTemplate(this.spaceTemplate); +} diff --git a/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart new file mode 100644 index 00000000..1a9f52bb --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/create_space_model_state.dart @@ -0,0 +1,19 @@ +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +abstract class CreateSpaceModelState {} + +class CreateSpaceModelInitial extends CreateSpaceModelState {} + +class CreateSpaceModelLoading extends CreateSpaceModelState {} + +class CreateSpaceModelLoaded extends CreateSpaceModelState { + final SpaceTemplateModel space; + + CreateSpaceModelLoaded(this.space); +} + +class CreateSpaceModelError extends CreateSpaceModelState { + final String message; + + CreateSpaceModelError(this.message); +} diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart index 423a5ca5..344d4b95 100644 --- a/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart @@ -1,38 +1,34 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; - -// Events -abstract class SubSpaceModelEvent {} - -class AddSubSpaceModel extends SubSpaceModelEvent { - final SubspaceTemplateModel subSpace; - AddSubSpaceModel(this.subSpace); -} - -class RemoveSubSpaceModel extends SubSpaceModelEvent { - final SubspaceTemplateModel subSpace; - RemoveSubSpaceModel(this.subSpace); -} - -// State -class SubSpaceModelState { - final List subSpaces; - SubSpaceModelState(this.subSpaces); -} +import 'package:syncrow_web/utils/constants/action_enum.dart'; // BLoC class SubSpaceModelBloc extends Bloc { - SubSpaceModelBloc() : super(SubSpaceModelState([])) { + SubSpaceModelBloc() : super(SubSpaceModelState([], [])) { on((event, emit) { final updatedSubSpaces = List.from(state.subSpaces) ..add(event.subSpace); - emit(SubSpaceModelState(updatedSubSpaces)); + emit(SubSpaceModelState(updatedSubSpaces, [])); }); on((event, emit) { final updatedSubSpaces = List.from(state.subSpaces) ..remove(event.subSpace); - emit(SubSpaceModelState(updatedSubSpaces)); + + final updatedSubspaceModels = List.from( + state.updatedSubSpaceModels, + ); + + if (event.subSpace.uuid?.isNotEmpty ?? false) { + updatedSubspaceModels.add(UpdateSubspaceTemplateModel( + action: Action.delete, + uuid: event.subSpace.uuid!, + )); + } + + emit(SubSpaceModelState(updatedSubSpaces, updatedSubspaceModels)); }); } } diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart new file mode 100644 index 00000000..07d85643 --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_event.dart @@ -0,0 +1,14 @@ +// Events +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +abstract class SubSpaceModelEvent {} + +class AddSubSpaceModel extends SubSpaceModelEvent { + final SubspaceTemplateModel subSpace; + AddSubSpaceModel(this.subSpace); +} + +class RemoveSubSpaceModel extends SubSpaceModelEvent { + final SubspaceTemplateModel subSpace; + RemoveSubSpaceModel(this.subSpace); +} diff --git a/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart new file mode 100644 index 00000000..1579f32f --- /dev/null +++ b/lib/pages/spaces_management/space_model/bloc/subspace_model_state.dart @@ -0,0 +1,11 @@ +import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; + +class SubSpaceModelState { + final List subSpaces; + final List updatedSubSpaceModels; + + SubSpaceModelState( + this.subSpaces, + this.updatedSubSpaceModels, + ); +} diff --git a/lib/pages/spaces_management/space_model/models/space_template_model.dart b/lib/pages/spaces_management/space_model/models/space_template_model.dart index 2107b87f..a885ea6c 100644 --- a/lib/pages/spaces_management/space_model/models/space_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/space_template_model.dart @@ -1,25 +1,20 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/utils/constants/action_enum.dart'; import 'package:uuid/uuid.dart'; class SpaceTemplateModel { - final String uuid; - final DateTime createdAt; - final DateTime updatedAt; + final String? uuid; final String modelName; - final bool disabled; - final List subspaceModels; - final List tags; + final List? subspaceModels; + final List? tags; String internalId; SpaceTemplateModel({ - required this.uuid, + this.uuid, String? internalId, - required this.createdAt, - required this.updatedAt, required this.modelName, - required this.disabled, - required this.subspaceModels, - required this.tags, + this.subspaceModels, + this.tags, }) : internalId = internalId ?? const Uuid().v4(); factory SpaceTemplateModel.fromJson(Map json) { @@ -28,28 +23,22 @@ class SpaceTemplateModel { return SpaceTemplateModel( uuid: json['uuid'] ?? '', internalId: internalId, - createdAt: DateTime.parse(json['createdAt']), - updatedAt: DateTime.parse(json['updatedAt']), modelName: json['modelName'] ?? '', - disabled: json['disabled'] ?? false, subspaceModels: (json['subspaceModels'] as List) - .map((item) => SubspaceTemplateModel.fromJson(item)) - .toList(), + .map((item) => SubspaceTemplateModel.fromJson(item)) + .toList(), tags: (json['tags'] as List) - .map((item) => TagModel.fromJson(item)) - .toList(), + .map((item) => TagModel.fromJson(item)) + .toList(), ); } Map toJson() { return { 'uuid': uuid, - 'createdAt': createdAt.toIso8601String(), - 'updatedAt': updatedAt.toIso8601String(), 'modelName': modelName, - 'disabled': disabled, - 'subspaceModels': subspaceModels.map((e) => e.toJson()).toList(), - 'tags': tags.map((e) => e.toJson()).toList(), + 'subspaceModels': subspaceModels?.map((e) => e.toJson()).toList(), + 'tags': tags?.map((e) => e.toJson()).toList(), }; } } @@ -129,3 +118,75 @@ class TagModel { }; } } + +class UpdateSubspaceTemplateModel { + final String uuid; + final Action action; + final String? subspaceName; + final List? tags; + + UpdateSubspaceTemplateModel({ + required this.action, + required this.uuid, + this.subspaceName, + this.tags, + }); + + factory UpdateSubspaceTemplateModel.fromJson(Map json) { + return UpdateSubspaceTemplateModel( + action: ActionExtension.fromValue(json['action']), + uuid: json['uuid'] ?? '', + subspaceName: json['subspaceName'] ?? '', + tags: (json['tags'] as List) + .map((item) => UpdateTagModel.fromJson(item)) + .toList(), + ); + } + + Map toJson() { + return { + 'action': action.value, + 'uuid': uuid, + 'subspaceName': subspaceName, + 'tags': tags?.map((e) => e.toJson()).toList() ?? [], + }; + } +} + +class UpdateTagModel { + final Action action; + final String? uuid; + final String tag; + final bool disabled; + final ProductModel? product; + + UpdateTagModel({ + required this.action, + this.uuid, + required this.tag, + required this.disabled, + this.product, + }); + + factory UpdateTagModel.fromJson(Map json) { + return UpdateTagModel( + action: ActionExtension.fromValue(json['action']), + uuid: json['uuid'] ?? '', + tag: json['tag'] ?? '', + disabled: json['disabled'] ?? false, + product: json['product'] != null + ? ProductModel.fromMap(json['product']) + : null, + ); + } + + Map toJson() { + return { + 'action': action.value, + 'uuid': uuid, + 'tag': tag, + 'disabled': disabled, + 'product': product?.toMap(), + }; + } +} diff --git a/lib/pages/spaces_management/space_model/view/space_model_page.dart b/lib/pages/spaces_management/space_model/view/space_model_page.dart index 432713db..dd36350a 100644 --- a/lib/pages/spaces_management/space_model/view/space_model_page.dart +++ b/lib/pages/spaces_management/space_model/view/space_model_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/space_model_card_widget.dart'; @@ -6,8 +7,10 @@ import 'package:syncrow_web/utils/color_manager.dart'; class SpaceModelPage extends StatelessWidget { final List spaceModels; + final List? products; - const SpaceModelPage({Key? key, required this.spaceModels}) : super(key: key); + const SpaceModelPage({Key? key, required this.spaceModels, this.products}) + : super(key: key); @override Widget build(BuildContext context) { @@ -33,7 +36,7 @@ class SpaceModelPage extends StatelessWidget { showDialog( context: context, builder: (BuildContext context) { - return const CreateSpaceModelDialog(); + return CreateSpaceModelDialog(products: products); }, ); }, diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart index 4f0fff01..2d14af22 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_space_model_dialog.dart @@ -1,12 +1,15 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; -import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSpaceModelDialog extends StatelessWidget { - const CreateSpaceModelDialog({Key? key}) : super(key: key); + final List? products; + + const CreateSpaceModelDialog({Key? key, this.products}) : super(key: key); @override Widget build(BuildContext context) { @@ -50,94 +53,54 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ), const SizedBox(height: 16), - GestureDetector( - onTap: () async { - final result = await showDialog( + TextButton( + onPressed: () async { + final result = await showDialog( + barrierDismissible: false, context: context, builder: (BuildContext context) { - return CreateSubSpaceModelDialog( + return const CreateSubSpaceModelDialog( isEdit: true, dialogTitle: 'Create Sub-space', - existingSubSpaces: [ - SubspaceTemplateModel( - subspaceName: "Living Room", - disabled: false, - uuid: "mkmkl,", - ), - ], ); }, ); - if (result == true) {} + if (result != null && result.isNotEmpty) { + // Handle the result if necessary + print('Subspace created: $result'); + } }, - child: SizedBox( - width: screenWidth * 0.25, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - border: Border.all( - color: ColorsManager.neutralGray, - width: 3.0, - ), - borderRadius: BorderRadius.circular(20), - ), - child: const Padding( - padding: - EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), - child: Row( - children: [ - Icon( - Icons.add, - color: ColorsManager.spaceColor, - ), - SizedBox(width: 10), - Expanded( - child: Text( - 'Create sub space', - style: TextStyle( - color: ColorsManager.blackColor, - fontSize: 16, - ), - ), - ), - ], - ), - ), - ), + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), + child: _buildButtonContent( + context, + icon: Icons.add, + label: 'Create Sub Space', ), ), const SizedBox(height: 10), - SizedBox( - width: screenWidth * 0.25, - child: Container( - decoration: BoxDecoration( - color: ColorsManager.textFieldGreyColor, - border: - Border.all(color: ColorsManager.neutralGray, width: 3.0), - borderRadius: BorderRadius.circular(20), - ), - child: const Padding( - padding: - EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), - child: Row( - children: [ - Icon( - Icons.add, - color: ColorsManager.spaceColor, - ), - SizedBox(width: 10), - Expanded( - child: Text( - 'Add devices', - style: TextStyle( - color: ColorsManager.blackColor, - fontSize: 16, - ), - ), - ), - ], + TextButton( + onPressed: () async { + final result = await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AddDeviceWidget( + products: products, ), - ), + ); + if (result == true) { + // Handle the result if necessary + print('Devices added successfully'); + } + }, + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), + child: _buildButtonContent( + context, + icon: Icons.add, + label: 'Add Devices', ), ), const SizedBox(height: 20), @@ -146,14 +109,17 @@ class CreateSpaceModelDialog extends StatelessWidget { child: Row( children: [ Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () => Navigator.of(context).pop(), - )), + child: CancelButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(), + ), + ), const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: () {}, + onPressed: () { + Navigator.of(context).pop(); + }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, foregroundColor: ColorsManager.whiteColors, @@ -162,10 +128,50 @@ class CreateSpaceModelDialog extends StatelessWidget { ), ], ), - ) + ), ], ), ), ); } + + Widget _buildButtonContent(BuildContext context, + {required IconData icon, required String label}) { + final screenWidth = MediaQuery.of(context).size.width; + + return SizedBox( + width: screenWidth * 0.25, + child: Container( + decoration: BoxDecoration( + color: ColorsManager.textFieldGreyColor, + border: Border.all( + color: ColorsManager.neutralGray, + width: 3.0, + ), + borderRadius: BorderRadius.circular(20), + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), + child: Row( + children: [ + Icon( + icon, + color: ColorsManager.spaceColor, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + label, + style: const TextStyle( + color: ColorsManager.blackColor, + fontSize: 16, + ), + ), + ), + ], + ), + ), + ), + ); + } } diff --git a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart index 9738f079..c4fec906 100644 --- a/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/space_model/widgets/dialog/create_subspace_model_dialog.dart @@ -3,13 +3,15 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/cancel_button.dart'; import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_event.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/bloc/subspace_model_state.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class CreateSubSpaceModelDialog extends StatefulWidget { - final bool isEdit; // Flag to determine if it's edit or create - final String dialogTitle; // Title for the dialog - final List? existingSubSpaces; // For edit mode + final bool isEdit; + final String dialogTitle; + final List? existingSubSpaces; const CreateSubSpaceModelDialog({ super.key, @@ -52,8 +54,17 @@ class _CreateSubSpaceModelDialogState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), + child: BlocProvider( - create: (_) => SubSpaceModelBloc(), + create: (_) { + final bloc = SubSpaceModelBloc(); + if (widget.existingSubSpaces != null) { + for (var subSpace in widget.existingSubSpaces!) { + bloc.add(AddSubSpaceModel(subSpace)); + } + } + return bloc; + }, child: BlocBuilder( builder: (context, state) { return SizedBox( diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index 550361c7..92d5735d 100644 --- a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -13,16 +13,20 @@ class SpaceModelCardWidget extends StatelessWidget { Widget build(BuildContext context) { final Map productTagCount = {}; - for (var tag in model.tags) { - final prodIcon = tag.product?.icon ?? 'Unknown'; - productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + if (model.tags != null) { + for (var tag in model.tags!) { + final prodIcon = tag.product?.icon ?? 'Unknown'; + productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + } } - for (var subspace in model.subspaceModels) { - if (subspace.tags != null) { - for (var tag in subspace.tags!) { - final prodIcon = tag.product?.icon ?? 'Unknown'; - productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + if (model.subspaceModels != null) { + for (var subspace in model.subspaceModels!) { + if (subspace.tags != null) { + for (var tag in subspace.tags!) { + final prodIcon = tag.product?.icon ?? 'Unknown'; + productTagCount[prodIcon] = (productTagCount[prodIcon] ?? 0) + 1; + } } } } @@ -65,7 +69,7 @@ class SpaceModelCardWidget extends StatelessWidget { spacing: 3.0, runSpacing: 3.0, children: [ - for (var subspace in model.subspaceModels.take(3)) + for (var subspace in model.subspaceModels!.take(3)) SubspaceChipWidget(subspace: subspace.subspaceName), ], ), diff --git a/lib/utils/constants/action_enum.dart b/lib/utils/constants/action_enum.dart new file mode 100644 index 00000000..2cfe53de --- /dev/null +++ b/lib/utils/constants/action_enum.dart @@ -0,0 +1,31 @@ +enum Action { + update, + add, + delete, +} + +extension ActionExtension on Action { + String get value { + switch (this) { + case Action.update: + return 'update'; + case Action.add: + return 'add'; + case Action.delete: + return 'delete'; + } + } + + static Action fromValue(String value) { + switch (value) { + case 'update': + return Action.update; + case 'add': + return Action.add; + case 'delete': + return Action.delete; + default: + throw ArgumentError('Invalid action: $value'); + } + } +}